use std::collections::HashMap;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Point {
pub x: f32,
pub y: f32,
}
impl Point {
pub const ZERO: Point = Point { x: 0.0, y: 0.0 };
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Size {
pub width: f32,
pub height: f32,
}
impl Size {
pub const ZERO: Size = Size {
width: 0.0,
height: 0.0,
};
pub const fn new(width: f32, height: f32) -> Self {
Self { width, height }
}
pub const fn to_rect(self) -> Rect {
Rect {
origin: Point::ZERO,
size: self,
}
}
}
impl From<Size> for Rect {
fn from(size: Size) -> Self {
Rect {
origin: Point::ZERO,
size,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Rect {
pub origin: Point,
pub size: Size,
}
impl Rect {
pub const ZERO: Rect = Rect {
origin: Point::ZERO,
size: Size::ZERO,
};
pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
Self {
origin: Point::new(x, y),
size: Size::new(width, height),
}
}
pub fn from_origin_size(origin: Point, size: Size) -> Self {
Self { origin, size }
}
pub fn x(&self) -> f32 {
self.origin.x
}
pub fn y(&self) -> f32 {
self.origin.y
}
pub fn width(&self) -> f32 {
self.size.width
}
pub fn height(&self) -> f32 {
self.size.height
}
pub fn center(&self) -> Point {
Point::new(
self.origin.x + self.size.width / 2.0,
self.origin.y + self.size.height / 2.0,
)
}
pub fn contains(&self, point: Point) -> 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
}
pub fn size(&self) -> Size {
self.size
}
pub fn offset(&self, dx: f32, dy: f32) -> Self {
Rect {
origin: Point::new(self.origin.x + dx, self.origin.y + dy),
size: self.size,
}
}
pub fn inset(&self, dx: f32, dy: f32) -> Self {
Rect {
origin: Point::new(self.origin.x + dx, self.origin.y + dy),
size: Size::new(
(self.size.width - 2.0 * dx).max(0.0),
(self.size.height - 2.0 * dy).max(0.0),
),
}
}
pub fn from_center(center: Point, size: Size) -> Self {
Rect {
origin: Point::new(center.x - size.width / 2.0, center.y - size.height / 2.0),
size,
}
}
pub fn from_points(p1: Point, p2: Point) -> Self {
let min_x = p1.x.min(p2.x);
let min_y = p1.y.min(p2.y);
let max_x = p1.x.max(p2.x);
let max_y = p1.y.max(p2.y);
Rect {
origin: Point::new(min_x, min_y),
size: Size::new(max_x - min_x, max_y - min_y),
}
}
pub fn union(&self, other: &Rect) -> Self {
let min_x = self.origin.x.min(other.origin.x);
let min_y = self.origin.y.min(other.origin.y);
let max_x = (self.origin.x + self.size.width).max(other.origin.x + other.size.width);
let max_y = (self.origin.y + self.size.height).max(other.origin.y + other.size.height);
Rect {
origin: Point::new(min_x, min_y),
size: Size::new(max_x - min_x, max_y - min_y),
}
}
pub fn expand_to_include(&self, point: Point) -> Self {
let min_x = self.origin.x.min(point.x);
let min_y = self.origin.y.min(point.y);
let max_x = (self.origin.x + self.size.width).max(point.x);
let max_y = (self.origin.y + self.size.height).max(point.y);
Rect {
origin: Point::new(min_x, min_y),
size: Size::new(max_x - min_x, max_y - min_y),
}
}
pub fn intersects(&self, other: &Rect) -> bool {
let self_right = self.origin.x + self.size.width;
let self_bottom = self.origin.y + self.size.height;
let other_right = other.origin.x + other.size.width;
let other_bottom = other.origin.y + other.size.height;
self.origin.x < other_right
&& self_right > other.origin.x
&& self.origin.y < other_bottom
&& self_bottom > other.origin.y
}
pub fn intersection(&self, other: &Rect) -> Option<Self> {
if !self.intersects(other) {
return None;
}
let x = self.origin.x.max(other.origin.x);
let y = 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);
Some(Rect {
origin: Point::new(x, y),
size: Size::new(right - x, bottom - y),
})
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
impl Vec2 {
pub const ZERO: Vec2 = Vec2 { x: 0.0, y: 0.0 };
pub const ONE: Vec2 = Vec2 { x: 1.0, y: 1.0 };
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub fn length(&self) -> f32 {
(self.x * self.x + self.y * self.y).sqrt()
}
pub fn normalize(&self) -> Self {
let len = self.length();
if len > 0.0 {
Self::new(self.x / len, self.y / len)
} else {
Self::ZERO
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Vec3 {
pub const ZERO: Vec3 = Vec3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
pub const ONE: Vec3 = Vec3 {
x: 1.0,
y: 1.0,
z: 1.0,
};
pub const UP: Vec3 = Vec3 {
x: 0.0,
y: 1.0,
z: 0.0,
};
pub const FORWARD: Vec3 = Vec3 {
x: 0.0,
y: 0.0,
z: -1.0,
};
pub const fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
pub fn length(&self) -> f32 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
pub fn normalize(&self) -> Self {
let len = self.length();
if len > 0.0 {
Self::new(self.x / len, self.y / len, self.z / len)
} else {
Self::ZERO
}
}
pub fn dot(&self, other: Vec3) -> f32 {
self.x * other.x + self.y * other.y + self.z * other.z
}
pub fn cross(&self, other: Vec3) -> Vec3 {
Vec3::new(
self.y * other.z - self.z * other.y,
self.z * other.x - self.x * other.z,
self.x * other.y - self.y * other.x,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Mat4 {
pub cols: [[f32; 4]; 4],
}
impl Default for Mat4 {
fn default() -> Self {
Self::IDENTITY
}
}
impl Mat4 {
pub const IDENTITY: Mat4 = Mat4 {
cols: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
};
pub fn translation(x: f32, y: f32, z: f32) -> Self {
Self {
cols: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[x, y, z, 1.0],
],
}
}
pub fn scale(x: f32, y: f32, z: f32) -> Self {
Self {
cols: [
[x, 0.0, 0.0, 0.0],
[0.0, y, 0.0, 0.0],
[0.0, 0.0, z, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
}
}
pub fn rotation_y(angle: f32) -> Self {
let c = angle.cos();
let s = angle.sin();
Self {
cols: [
[c, 0.0, -s, 0.0],
[0.0, 1.0, 0.0, 0.0],
[s, 0.0, c, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
}
}
pub fn mul(&self, other: &Mat4) -> Mat4 {
let mut result = [[0.0f32; 4]; 4];
for (i, result_col) in result.iter_mut().enumerate() {
for (j, result_elem) in result_col.iter_mut().enumerate() {
for k in 0..4 {
*result_elem += self.cols[k][j] * other.cols[i][k];
}
}
}
Mat4 { cols: result }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Affine2D {
pub elements: [f32; 6],
}
impl Default for Affine2D {
fn default() -> Self {
Self::IDENTITY
}
}
impl Affine2D {
pub const IDENTITY: Affine2D = Affine2D {
elements: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
};
pub fn translation(x: f32, y: f32) -> Self {
Self {
elements: [1.0, 0.0, 0.0, 1.0, x, y],
}
}
pub fn scale(sx: f32, sy: f32) -> Self {
Self {
elements: [sx, 0.0, 0.0, sy, 0.0, 0.0],
}
}
pub fn rotation(angle: f32) -> Self {
let c = angle.cos();
let s = angle.sin();
Self {
elements: [c, s, -s, c, 0.0, 0.0],
}
}
pub fn skew_x(angle: f32) -> Self {
Self {
elements: [1.0, 0.0, angle.tan(), 1.0, 0.0, 0.0],
}
}
pub fn skew_y(angle: f32) -> Self {
Self {
elements: [1.0, angle.tan(), 0.0, 1.0, 0.0, 0.0],
}
}
pub fn transform_point(&self, point: Point) -> Point {
let [a, b, c, d, tx, ty] = self.elements;
Point::new(
a * point.x + c * point.y + tx,
b * point.x + d * point.y + ty,
)
}
pub fn then(&self, other: &Affine2D) -> Affine2D {
let [a1, b1, c1, d1, tx1, ty1] = self.elements;
let [a2, b2, c2, d2, tx2, ty2] = other.elements;
Affine2D {
elements: [
a1 * a2 + c1 * b2, b1 * a2 + d1 * b2, a1 * c2 + c1 * d2, b1 * c2 + d1 * d2, a1 * tx2 + c1 * ty2 + tx1, b1 * tx2 + d1 * ty2 + ty1, ],
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Color {
pub const WHITE: Color = Color::rgb(1.0, 1.0, 1.0);
pub const BLACK: Color = Color::rgb(0.0, 0.0, 0.0);
pub const RED: Color = Color::rgb(1.0, 0.0, 0.0);
pub const GREEN: Color = Color::rgb(0.0, 1.0, 0.0);
pub const BLUE: Color = Color::rgb(0.0, 0.0, 1.0);
pub const YELLOW: Color = Color::rgb(1.0, 1.0, 0.0);
pub const CYAN: Color = Color::rgb(0.0, 1.0, 1.0);
pub const MAGENTA: Color = Color::rgb(1.0, 0.0, 1.0);
pub const PURPLE: Color = Color::rgb(0.5, 0.0, 0.5);
pub const ORANGE: Color = Color::rgb(1.0, 0.5, 0.0);
pub const GRAY: Color = Color::rgb(0.5, 0.5, 0.5);
pub const TRANSPARENT: Color = Color::rgba(0.0, 0.0, 0.0, 0.0);
pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
Self { r, g, b, a: 1.0 }
}
pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
Self { r, g, b, a }
}
pub fn from_hex(hex: u32) -> Self {
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
let b = (hex & 0xFF) as f32 / 255.0;
Self::rgb(r, g, b)
}
pub fn with_alpha(mut self, alpha: f32) -> Self {
self.a = alpha;
self
}
pub fn to_array(&self) -> [f32; 4] {
[self.r, self.g, self.b, self.a]
}
pub fn lerp(a: &Color, b: &Color, t: f32) -> Color {
let t = t.clamp(0.0, 1.0);
Color {
r: a.r + (b.r - a.r) * t,
g: a.g + (b.g - a.g) * t,
b: a.b + (b.b - a.b) * t,
a: a.a + (b.a - a.a) * t,
}
}
}
impl Default for Color {
fn default() -> Self {
Self::BLACK
}
}
#[derive(Clone, Copy, Debug)]
pub struct GradientStop {
pub offset: f32,
pub color: Color,
}
impl GradientStop {
pub fn new(offset: f32, color: Color) -> Self {
Self {
offset: offset.clamp(0.0, 1.0),
color,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum GradientSpace {
#[default]
UserSpace,
ObjectBoundingBox,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum GradientSpread {
#[default]
Pad,
Reflect,
Repeat,
}
#[derive(Clone, Debug)]
pub enum Gradient {
Linear {
start: Point,
end: Point,
stops: Vec<GradientStop>,
space: GradientSpace,
spread: GradientSpread,
},
Radial {
center: Point,
radius: f32,
focal: Option<Point>,
stops: Vec<GradientStop>,
space: GradientSpace,
spread: GradientSpread,
},
Conic {
center: Point,
start_angle: f32,
stops: Vec<GradientStop>,
space: GradientSpace,
},
}
impl Gradient {
pub fn linear(start: Point, end: Point, from: Color, to: Color) -> Self {
Gradient::Linear {
start,
end,
stops: vec![GradientStop::new(0.0, from), GradientStop::new(1.0, to)],
space: GradientSpace::UserSpace,
spread: GradientSpread::Pad,
}
}
pub fn linear_with_stops(start: Point, end: Point, stops: Vec<GradientStop>) -> Self {
Gradient::Linear {
start,
end,
stops,
space: GradientSpace::UserSpace,
spread: GradientSpread::Pad,
}
}
pub fn radial(center: Point, radius: f32, from: Color, to: Color) -> Self {
Gradient::Radial {
center,
radius,
focal: None,
stops: vec![GradientStop::new(0.0, from), GradientStop::new(1.0, to)],
space: GradientSpace::UserSpace,
spread: GradientSpread::Pad,
}
}
pub fn radial_with_stops(center: Point, radius: f32, stops: Vec<GradientStop>) -> Self {
Gradient::Radial {
center,
radius,
focal: None,
stops,
space: GradientSpace::UserSpace,
spread: GradientSpread::Pad,
}
}
pub fn conic(center: Point, from: Color, to: Color) -> Self {
Gradient::Conic {
center,
start_angle: 0.0,
stops: vec![GradientStop::new(0.0, from), GradientStop::new(1.0, to)],
space: GradientSpace::UserSpace,
}
}
pub fn stops(&self) -> &[GradientStop] {
match self {
Gradient::Linear { stops, .. } => stops,
Gradient::Radial { stops, .. } => stops,
Gradient::Conic { stops, .. } => stops,
}
}
pub fn first_color(&self) -> Color {
self.stops()
.first()
.map(|s| s.color)
.unwrap_or(Color::BLACK)
}
pub fn last_color(&self) -> Color {
self.stops().last().map(|s| s.color).unwrap_or(Color::BLACK)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ImageFit {
#[default]
Cover,
Contain,
Fill,
Tile,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ImagePosition {
pub x: f32,
pub y: f32,
}
impl ImagePosition {
pub const CENTER: Self = Self { x: 0.5, y: 0.5 };
pub const TOP_LEFT: Self = Self { x: 0.0, y: 0.0 };
pub const TOP_CENTER: Self = Self { x: 0.5, y: 0.0 };
pub const TOP_RIGHT: Self = Self { x: 1.0, y: 0.0 };
pub const CENTER_LEFT: Self = Self { x: 0.0, y: 0.5 };
pub const CENTER_RIGHT: Self = Self { x: 1.0, y: 0.5 };
pub const BOTTOM_LEFT: Self = Self { x: 0.0, y: 1.0 };
pub const BOTTOM_CENTER: Self = Self { x: 0.5, y: 1.0 };
pub const BOTTOM_RIGHT: Self = Self { x: 1.0, y: 1.0 };
pub fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
#[derive(Clone, Debug)]
pub struct ImageBrush {
pub source: String,
pub fit: ImageFit,
pub position: ImagePosition,
pub opacity: f32,
pub tint: Color,
}
impl ImageBrush {
pub fn new(source: impl Into<String>) -> Self {
Self {
source: source.into(),
fit: ImageFit::Cover,
position: ImagePosition::CENTER,
opacity: 1.0,
tint: Color::WHITE,
}
}
pub fn fit(mut self, fit: ImageFit) -> Self {
self.fit = fit;
self
}
pub fn cover(self) -> Self {
self.fit(ImageFit::Cover)
}
pub fn contain(self) -> Self {
self.fit(ImageFit::Contain)
}
pub fn fill(self) -> Self {
self.fit(ImageFit::Fill)
}
pub fn tile(self) -> Self {
self.fit(ImageFit::Tile)
}
pub fn position(mut self, position: ImagePosition) -> Self {
self.position = position;
self
}
pub fn center(self) -> Self {
self.position(ImagePosition::CENTER)
}
pub fn opacity(mut self, opacity: f32) -> Self {
self.opacity = opacity;
self
}
pub fn tint(mut self, color: Color) -> Self {
self.tint = color;
self
}
}
#[derive(Clone, Debug)]
pub enum Brush {
Solid(Color),
Gradient(Gradient),
Glass(GlassStyle),
Blur(BlurStyle),
Image(ImageBrush),
}
impl From<Color> for Brush {
fn from(color: Color) -> Self {
Brush::Solid(color)
}
}
impl From<GlassStyle> for Brush {
fn from(style: GlassStyle) -> Self {
Brush::Glass(style)
}
}
impl From<ImageBrush> for Brush {
fn from(brush: ImageBrush) -> Self {
Brush::Image(brush)
}
}
impl From<BlurStyle> for Brush {
fn from(style: BlurStyle) -> Self {
Brush::Blur(style)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum BlendMode {
#[default]
Normal,
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct CornerRadius {
pub top_left: f32,
pub top_right: f32,
pub bottom_right: f32,
pub bottom_left: f32,
}
impl CornerRadius {
pub const ZERO: CornerRadius = CornerRadius {
top_left: 0.0,
top_right: 0.0,
bottom_right: 0.0,
bottom_left: 0.0,
};
pub fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
Self {
top_left,
top_right,
bottom_right,
bottom_left,
}
}
pub fn uniform(radius: f32) -> Self {
Self {
top_left: radius,
top_right: radius,
bottom_right: radius,
bottom_left: radius,
}
}
pub fn to_array(&self) -> [f32; 4] {
[
self.top_left,
self.top_right,
self.bottom_right,
self.bottom_left,
]
}
pub fn is_uniform(&self) -> bool {
self.top_left == self.top_right
&& self.top_right == self.bottom_right
&& self.bottom_right == self.bottom_left
}
}
impl From<f32> for CornerRadius {
fn from(radius: f32) -> Self {
Self::uniform(radius)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CornerShape {
pub top_left: f32,
pub top_right: f32,
pub bottom_right: f32,
pub bottom_left: f32,
}
impl Default for CornerShape {
fn default() -> Self {
Self::ROUND
}
}
impl CornerShape {
pub const ROUND: CornerShape = CornerShape {
top_left: 1.0,
top_right: 1.0,
bottom_right: 1.0,
bottom_left: 1.0,
};
pub const BEVEL: CornerShape = CornerShape {
top_left: 0.0,
top_right: 0.0,
bottom_right: 0.0,
bottom_left: 0.0,
};
pub const SQUIRCLE: CornerShape = CornerShape {
top_left: 2.0,
top_right: 2.0,
bottom_right: 2.0,
bottom_left: 2.0,
};
pub const SCOOP: CornerShape = CornerShape {
top_left: -1.0,
top_right: -1.0,
bottom_right: -1.0,
bottom_left: -1.0,
};
pub const NOTCH: CornerShape = CornerShape {
top_left: -100.0,
top_right: -100.0,
bottom_right: -100.0,
bottom_left: -100.0,
};
pub const SQUARE: CornerShape = CornerShape {
top_left: 100.0,
top_right: 100.0,
bottom_right: 100.0,
bottom_left: 100.0,
};
pub fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
Self {
top_left,
top_right,
bottom_right,
bottom_left,
}
}
pub fn uniform(n: f32) -> Self {
Self {
top_left: n,
top_right: n,
bottom_right: n,
bottom_left: n,
}
}
pub fn to_array(&self) -> [f32; 4] {
[
self.top_left,
self.top_right,
self.bottom_right,
self.bottom_left,
]
}
pub fn is_round(&self) -> bool {
(self.top_left - 1.0).abs() < 0.001
&& (self.top_right - 1.0).abs() < 0.001
&& (self.bottom_right - 1.0).abs() < 0.001
&& (self.bottom_left - 1.0).abs() < 0.001
}
}
impl From<f32> for CornerShape {
fn from(n: f32) -> Self {
Self::uniform(n)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct OverflowFade {
pub top: f32,
pub right: f32,
pub bottom: f32,
pub left: f32,
}
impl OverflowFade {
pub const NONE: OverflowFade = OverflowFade {
top: 0.0,
right: 0.0,
bottom: 0.0,
left: 0.0,
};
pub fn uniform(distance: f32) -> Self {
Self {
top: distance,
right: distance,
bottom: distance,
left: distance,
}
}
pub fn vertical(distance: f32) -> Self {
Self {
top: distance,
right: 0.0,
bottom: distance,
left: 0.0,
}
}
pub fn horizontal(distance: f32) -> Self {
Self {
top: 0.0,
right: distance,
bottom: 0.0,
left: distance,
}
}
pub fn new(top: f32, right: f32, bottom: f32, left: f32) -> Self {
Self {
top,
right,
bottom,
left,
}
}
pub fn to_array(&self) -> [f32; 4] {
[self.top, self.right, self.bottom, self.left]
}
pub fn is_none(&self) -> bool {
self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0 && self.left == 0.0
}
}
impl From<f32> for OverflowFade {
fn from(distance: f32) -> Self {
Self::uniform(distance)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Shadow {
pub offset_x: f32,
pub offset_y: f32,
pub blur: f32,
pub spread: f32,
pub color: Color,
}
impl Shadow {
pub fn new(offset_x: f32, offset_y: f32, blur: f32, color: Color) -> Self {
Self {
offset_x,
offset_y,
blur,
spread: 0.0,
color,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct GlassStyle {
pub blur: f32,
pub tint: Color,
pub saturation: f32,
pub brightness: f32,
pub noise: f32,
pub border_thickness: f32,
pub shadow: Option<Shadow>,
pub simple: bool,
pub depth: u32,
pub border_color: Option<Color>,
}
impl Default for GlassStyle {
fn default() -> Self {
Self {
blur: 20.0,
tint: Color::rgba(1.0, 1.0, 1.0, 0.1),
saturation: 1.0,
brightness: 1.0,
noise: 0.0,
border_thickness: 0.8,
shadow: None,
simple: false,
depth: 0,
border_color: None,
}
}
}
impl GlassStyle {
pub fn new() -> Self {
Self::default()
}
pub fn blur(mut self, blur: f32) -> Self {
self.blur = blur;
self
}
pub fn tint(mut self, color: Color) -> Self {
self.tint = color;
self
}
pub fn saturation(mut self, saturation: f32) -> Self {
self.saturation = saturation;
self
}
pub fn brightness(mut self, brightness: f32) -> Self {
self.brightness = brightness;
self
}
pub fn noise(mut self, noise: f32) -> Self {
self.noise = noise;
self
}
pub fn border(mut self, thickness: f32) -> Self {
self.border_thickness = thickness;
self
}
pub fn shadow(mut self, shadow: Shadow) -> Self {
self.shadow = Some(shadow);
self
}
pub fn ultra_thin() -> Self {
Self::new().blur(10.0)
}
pub fn thin() -> Self {
Self::new().blur(15.0)
}
pub fn regular() -> Self {
Self::new()
}
pub fn thick() -> Self {
Self::new().blur(30.0)
}
pub fn frosted() -> Self {
Self::new().noise(0.03)
}
pub fn simple() -> Self {
Self {
blur: 15.0,
tint: Color::rgba(1.0, 1.0, 1.0, 0.15),
saturation: 1.1,
brightness: 1.0,
noise: 0.0,
border_thickness: 0.0,
shadow: None,
simple: true,
depth: 0,
border_color: None,
}
}
pub fn with_simple(mut self, simple: bool) -> Self {
self.simple = simple;
self
}
}
#[derive(Clone, Copy, Debug)]
pub struct BlurStyle {
pub radius: f32,
pub quality: crate::draw::BlurQuality,
pub tint: Option<Color>,
pub opacity: f32,
}
impl Default for BlurStyle {
fn default() -> Self {
Self {
radius: 10.0,
quality: crate::draw::BlurQuality::Medium,
tint: None,
opacity: 1.0,
}
}
}
impl BlurStyle {
pub fn new() -> Self {
Self::default()
}
pub fn with_radius(radius: f32) -> Self {
Self {
radius,
..Default::default()
}
}
pub fn radius(mut self, radius: f32) -> Self {
self.radius = radius;
self
}
pub fn quality(mut self, quality: crate::draw::BlurQuality) -> Self {
self.quality = quality;
self
}
pub fn tint(mut self, color: Color) -> Self {
self.tint = Some(color);
self
}
pub fn opacity(mut self, opacity: f32) -> Self {
self.opacity = opacity;
self
}
pub fn light() -> Self {
Self::with_radius(5.0)
}
pub fn medium() -> Self {
Self::with_radius(10.0)
}
pub fn heavy() -> Self {
Self::with_radius(20.0)
}
pub fn max() -> Self {
Self::with_radius(50.0)
}
pub fn high_quality(mut self) -> Self {
self.quality = crate::draw::BlurQuality::High;
self
}
pub fn low_quality(mut self) -> Self {
self.quality = crate::draw::BlurQuality::Low;
self
}
}
impl From<f32> for BlurStyle {
fn from(radius: f32) -> Self {
Self::with_radius(radius)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct LayerId(pub u64);
impl LayerId {
pub fn new(id: u64) -> Self {
Self(id)
}
}
#[derive(Debug, Default)]
pub struct LayerIdGenerator {
next: u64,
}
impl LayerIdGenerator {
pub fn new() -> Self {
Self { next: 1 }
}
pub fn next_id(&mut self) -> LayerId {
let id = LayerId(self.next);
self.next += 1;
id
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PointerEvents {
#[default]
Auto,
None,
PassThrough,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum BillboardFacing {
#[default]
Camera,
CameraY,
Fixed,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum CachePolicy {
#[default]
None,
Content,
Manual,
}
#[derive(Clone, Debug)]
pub enum PostEffect {
Blur { radius: f32 },
Saturation { factor: f32 },
Brightness { factor: f32 },
Contrast { factor: f32 },
GlassBlur { radius: f32, tint: Color },
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TextureFormat {
#[default]
Bgra8Unorm,
Rgba8Unorm,
Rgba16Float,
Rgba32Float,
}
#[derive(Clone, Debug, Default)]
pub struct LayerProperties {
pub id: Option<LayerId>,
pub visible: bool,
pub pointer_events: PointerEvents,
pub order: i32,
pub name: Option<String>,
}
impl LayerProperties {
pub fn new() -> Self {
Self {
visible: true,
..Default::default()
}
}
pub fn with_id(mut self, id: LayerId) -> Self {
self.id = Some(id);
self
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn hidden(mut self) -> Self {
self.visible = false;
self
}
pub fn with_order(mut self, order: i32) -> Self {
self.order = order;
self
}
}
#[derive(Clone, Debug)]
pub enum ClipShape {
Rect(Rect),
RoundedRect {
rect: Rect,
corner_radius: CornerRadius,
},
Circle { center: Point, radius: f32 },
Ellipse { center: Point, radii: Vec2 },
Path(crate::draw::Path),
Polygon(Vec<Point>),
}
impl ClipShape {
pub fn rect(rect: Rect) -> Self {
ClipShape::Rect(rect)
}
pub fn rounded_rect(rect: Rect, corner_radius: impl Into<CornerRadius>) -> Self {
ClipShape::RoundedRect {
rect,
corner_radius: corner_radius.into(),
}
}
pub fn circle(center: Point, radius: f32) -> Self {
ClipShape::Circle { center, radius }
}
pub fn ellipse(center: Point, radii: Vec2) -> Self {
ClipShape::Ellipse { center, radii }
}
pub fn path(path: crate::draw::Path) -> Self {
ClipShape::Path(path)
}
pub fn bounds(&self) -> Rect {
match self {
ClipShape::Rect(rect) => *rect,
ClipShape::RoundedRect { rect, .. } => *rect,
ClipShape::Circle { center, radius } => {
Rect::from_center(*center, Size::new(*radius * 2.0, *radius * 2.0))
}
ClipShape::Ellipse { center, radii } => {
Rect::from_center(*center, Size::new(radii.x * 2.0, radii.y * 2.0))
}
ClipShape::Path(path) => path.bounds(),
ClipShape::Polygon(pts) => {
if pts.is_empty() {
return Rect::new(0.0, 0.0, 0.0, 0.0);
}
let min_x = pts.iter().map(|p| p.x).fold(f32::MAX, f32::min);
let min_y = pts.iter().map(|p| p.y).fold(f32::MAX, f32::min);
let max_x = pts.iter().map(|p| p.x).fold(f32::MIN, f32::max);
let max_y = pts.iter().map(|p| p.y).fold(f32::MIN, f32::max);
Rect::new(min_x, min_y, max_x - min_x, max_y - min_y)
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ClipLength {
Px(f32),
Percent(f32),
}
impl ClipLength {
pub fn resolve(&self, reference: f32) -> f32 {
match self {
ClipLength::Px(v) => *v,
ClipLength::Percent(pct) => reference * pct / 100.0,
}
}
}
impl Default for ClipLength {
fn default() -> Self {
ClipLength::Px(0.0)
}
}
#[derive(Clone, Debug)]
pub enum ClipPath {
Circle {
radius: Option<ClipLength>,
center: (ClipLength, ClipLength),
},
Ellipse {
rx: Option<ClipLength>,
ry: Option<ClipLength>,
center: (ClipLength, ClipLength),
},
Inset {
top: ClipLength,
right: ClipLength,
bottom: ClipLength,
left: ClipLength,
round: Option<f32>,
},
Rect {
top: ClipLength,
right: ClipLength,
bottom: ClipLength,
left: ClipLength,
round: Option<f32>,
},
Xywh {
x: ClipLength,
y: ClipLength,
w: ClipLength,
h: ClipLength,
round: Option<f32>,
},
Polygon {
points: Vec<(ClipLength, ClipLength)>,
},
Path { vertices: Vec<(f32, f32)> },
}
#[derive(Clone, Copy, Debug)]
pub enum CameraProjection {
Perspective {
fov_y: f32,
aspect: f32,
near: f32,
far: f32,
},
Orthographic {
left: f32,
right: f32,
bottom: f32,
top: f32,
near: f32,
far: f32,
},
}
impl Default for CameraProjection {
fn default() -> Self {
CameraProjection::Perspective {
fov_y: std::f32::consts::FRAC_PI_4,
aspect: 16.0 / 9.0,
near: 0.1,
far: 1000.0,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Camera {
pub position: Vec3,
pub target: Vec3,
pub up: Vec3,
pub projection: CameraProjection,
}
impl Camera {
pub fn perspective(position: Vec3, target: Vec3, fov_y: f32) -> Self {
Self {
position,
target,
up: Vec3::UP,
projection: CameraProjection::Perspective {
fov_y,
aspect: 16.0 / 9.0,
near: 0.1,
far: 1000.0,
},
}
}
pub fn orthographic(position: Vec3, target: Vec3, scale: f32) -> Self {
Self {
position,
target,
up: Vec3::UP,
projection: CameraProjection::Orthographic {
left: -scale,
right: scale,
bottom: -scale,
top: scale,
near: 0.1,
far: 1000.0,
},
}
}
}
#[derive(Clone, Debug)]
pub enum Light {
Directional {
direction: Vec3,
color: Color,
intensity: f32,
cast_shadows: bool,
},
Point {
position: Vec3,
color: Color,
intensity: f32,
range: f32,
},
Spot {
position: Vec3,
direction: Vec3,
color: Color,
intensity: f32,
range: f32,
inner_angle: f32,
outer_angle: f32,
},
Ambient {
color: Color,
intensity: f32,
},
}
#[derive(Clone, Debug, Default)]
pub struct Environment {
pub hdri: Option<String>,
pub intensity: f32,
pub blur: f32,
pub background_color: Option<Color>,
}
#[derive(Clone, Debug)]
pub struct CubemapData {
pub faces: Vec<Vec<u8>>,
pub size: u32,
pub mip_count: u32,
}
#[derive(Clone, Debug)]
pub struct Sdf3DViewport {
pub shader_wgsl: String,
pub camera_pos: Vec3,
pub camera_dir: Vec3,
pub camera_up: Vec3,
pub camera_right: Vec3,
pub fov: f32,
pub time: f32,
pub max_steps: u32,
pub max_distance: f32,
pub epsilon: f32,
pub lights: Vec<Light>,
}
impl Default for Sdf3DViewport {
fn default() -> Self {
Self {
shader_wgsl: String::new(),
camera_pos: Vec3::new(0.0, 2.0, 5.0),
camera_dir: Vec3::new(0.0, 0.0, -1.0),
camera_up: Vec3::new(0.0, 1.0, 0.0),
camera_right: Vec3::new(1.0, 0.0, 0.0),
fov: 0.8,
time: 0.0,
max_steps: 128,
max_distance: 100.0,
epsilon: 0.001,
lights: Vec::new(),
}
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub enum ParticleEmitterShape {
#[default]
Point,
Sphere { radius: f32 },
Hemisphere { radius: f32 },
Cone { angle: f32, radius: f32 },
Box { half_extents: Vec3 },
Circle { radius: f32 },
}
#[derive(Clone, Debug, PartialEq)]
pub enum ParticleForce {
Gravity(Vec3),
Wind {
direction: Vec3,
strength: f32,
turbulence: f32,
},
Vortex {
axis: Vec3,
center: Vec3,
strength: f32,
},
Drag(f32),
Turbulence { strength: f32, frequency: f32 },
Attractor { position: Vec3, strength: f32 },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ParticleRenderMode {
#[default]
Billboard,
Stretched,
Horizontal,
Vertical,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ParticleBlendMode {
#[default]
Alpha,
Additive,
Multiply,
}
#[derive(Clone, Debug)]
pub struct ParticleSystemData {
pub max_particles: u32,
pub emitter: ParticleEmitterShape,
pub emitter_position: Vec3,
pub emission_rate: f32,
pub burst_count: f32,
pub direction: Vec3,
pub direction_randomness: f32,
pub lifetime: (f32, f32),
pub start_speed: (f32, f32),
pub start_size: (f32, f32),
pub end_size: (f32, f32),
pub start_color: Color,
pub mid_color: Color,
pub end_color: Color,
pub forces: Vec<ParticleForce>,
pub gravity_scale: f32,
pub render_mode: ParticleRenderMode,
pub blend_mode: ParticleBlendMode,
pub time: f32,
pub delta_time: f32,
pub camera_pos: Vec3,
pub camera_dir: Vec3,
pub camera_up: Vec3,
pub camera_right: Vec3,
pub playing: bool,
}
impl Default for ParticleSystemData {
fn default() -> Self {
Self {
max_particles: 10000,
emitter: ParticleEmitterShape::Point,
emitter_position: Vec3::ZERO,
emission_rate: 100.0,
burst_count: 0.0,
direction: Vec3::new(0.0, 1.0, 0.0),
direction_randomness: 0.0,
lifetime: (1.0, 2.0),
start_speed: (1.0, 2.0),
start_size: (0.1, 0.2),
end_size: (0.0, 0.1),
start_color: Color::WHITE,
mid_color: Color::rgba(1.0, 1.0, 1.0, 0.5),
end_color: Color::rgba(1.0, 1.0, 1.0, 0.0),
forces: Vec::new(),
gravity_scale: 1.0,
render_mode: ParticleRenderMode::Billboard,
blend_mode: ParticleBlendMode::Alpha,
time: 0.0,
delta_time: 0.016,
camera_pos: Vec3::new(0.0, 2.0, 5.0),
camera_dir: Vec3::new(0.0, 0.0, -1.0),
camera_up: Vec3::new(0.0, 1.0, 0.0),
camera_right: Vec3::new(1.0, 0.0, 0.0),
playing: true,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Canvas2DCommands {
commands: Vec<Canvas2DCommand>,
}
#[derive(Clone, Debug)]
pub enum Canvas2DCommand {
Clear(Color),
Save,
Restore,
Transform(Affine2D),
SetOpacity(f32),
SetBlendMode(BlendMode),
PushClip(ClipShape),
PopClip,
FillPath {
path: crate::draw::Path,
brush: Brush,
},
StrokePath {
path: crate::draw::Path,
stroke: crate::draw::Stroke,
brush: Brush,
},
FillRect {
rect: Rect,
corner_radius: CornerRadius,
brush: Brush,
},
StrokeRect {
rect: Rect,
corner_radius: CornerRadius,
stroke: crate::draw::Stroke,
brush: Brush,
},
FillCircle {
center: Point,
radius: f32,
brush: Brush,
},
StrokeCircle {
center: Point,
radius: f32,
stroke: crate::draw::Stroke,
brush: Brush,
},
DrawText {
text: String,
origin: Point,
style: crate::draw::TextStyle,
},
DrawImage {
image: crate::draw::ImageId,
rect: Rect,
options: crate::draw::ImageOptions,
},
DrawShadow {
rect: Rect,
corner_radius: CornerRadius,
shadow: Shadow,
},
}
impl Canvas2DCommands {
pub fn new() -> Self {
Self {
commands: Vec::new(),
}
}
pub fn push(&mut self, command: Canvas2DCommand) {
self.commands.push(command);
}
pub fn commands(&self) -> &[Canvas2DCommand] {
&self.commands
}
pub fn clear(&mut self) {
self.commands.clear();
}
pub fn len(&self) -> usize {
self.commands.len()
}
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
}
#[derive(Clone, Debug, Default)]
pub struct Scene3DCommands {
commands: Vec<Scene3DCommand>,
}
#[derive(Clone, Debug)]
pub enum Scene3DCommand {
Clear(Color),
SetCamera(Camera),
PushTransform(Mat4),
PopTransform,
DrawMesh {
mesh: crate::draw::MeshId,
material: crate::draw::MaterialId,
transform: Mat4,
},
DrawMeshInstanced {
mesh: crate::draw::MeshId,
instances: Vec<crate::draw::MeshInstance>,
},
AddLight(Light),
SetEnvironment(Environment),
DrawBillboard {
size: Size,
transform: Mat4,
facing: BillboardFacing,
content: Canvas2DCommands,
},
}
impl Scene3DCommands {
pub fn new() -> Self {
Self {
commands: Vec::new(),
}
}
pub fn push(&mut self, command: Scene3DCommand) {
self.commands.push(command);
}
pub fn commands(&self) -> &[Scene3DCommand] {
&self.commands
}
pub fn clear(&mut self) {
self.commands.clear();
}
pub fn len(&self) -> usize {
self.commands.len()
}
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
}
#[derive(Clone, Copy, Debug)]
pub struct UiNode {
pub id: u64,
}
impl UiNode {
pub fn new(id: u64) -> Self {
Self { id }
}
}
#[derive(Clone, Debug)]
pub enum Layer {
Ui {
node: UiNode,
props: LayerProperties,
},
Canvas2D {
size: Size,
commands: Canvas2DCommands,
cache_policy: CachePolicy,
props: LayerProperties,
},
Scene3D {
viewport: Rect,
commands: Scene3DCommands,
camera: Camera,
environment: Option<Environment>,
props: LayerProperties,
},
Stack {
layers: Vec<Layer>,
blend_mode: BlendMode,
props: LayerProperties,
},
Transform2D {
transform: Affine2D,
layer: Box<Layer>,
props: LayerProperties,
},
Transform3D {
transform: Mat4,
layer: Box<Layer>,
props: LayerProperties,
},
Clip {
shape: ClipShape,
layer: Box<Layer>,
props: LayerProperties,
},
Opacity {
value: f32,
layer: Box<Layer>,
props: LayerProperties,
},
Offscreen {
size: Size,
format: TextureFormat,
layer: Box<Layer>,
effects: Vec<PostEffect>,
props: LayerProperties,
},
Billboard {
layer: Box<Layer>,
transform: Mat4,
facing: BillboardFacing,
props: LayerProperties,
},
Viewport3D {
rect: Rect,
scene: Box<Layer>, props: LayerProperties,
},
Portal {
source: LayerId,
sample_rect: Rect,
dest_rect: Rect,
props: LayerProperties,
},
Empty { props: LayerProperties },
}
impl Layer {
pub fn props(&self) -> &LayerProperties {
match self {
Layer::Ui { props, .. } => props,
Layer::Canvas2D { props, .. } => props,
Layer::Scene3D { props, .. } => props,
Layer::Stack { props, .. } => props,
Layer::Transform2D { props, .. } => props,
Layer::Transform3D { props, .. } => props,
Layer::Clip { props, .. } => props,
Layer::Opacity { props, .. } => props,
Layer::Offscreen { props, .. } => props,
Layer::Billboard { props, .. } => props,
Layer::Viewport3D { props, .. } => props,
Layer::Portal { props, .. } => props,
Layer::Empty { props } => props,
}
}
pub fn props_mut(&mut self) -> &mut LayerProperties {
match self {
Layer::Ui { props, .. } => props,
Layer::Canvas2D { props, .. } => props,
Layer::Scene3D { props, .. } => props,
Layer::Stack { props, .. } => props,
Layer::Transform2D { props, .. } => props,
Layer::Transform3D { props, .. } => props,
Layer::Clip { props, .. } => props,
Layer::Opacity { props, .. } => props,
Layer::Offscreen { props, .. } => props,
Layer::Billboard { props, .. } => props,
Layer::Viewport3D { props, .. } => props,
Layer::Portal { props, .. } => props,
Layer::Empty { props } => props,
}
}
pub fn id(&self) -> Option<LayerId> {
self.props().id
}
pub fn is_visible(&self) -> bool {
self.props().visible
}
pub fn empty() -> Self {
Layer::Empty {
props: LayerProperties::new(),
}
}
pub fn stack(layers: Vec<Layer>) -> Self {
Layer::Stack {
layers,
blend_mode: BlendMode::Normal,
props: LayerProperties::new(),
}
}
pub fn with_transform_2d(self, transform: Affine2D) -> Self {
Layer::Transform2D {
transform,
layer: Box::new(self),
props: LayerProperties::new(),
}
}
pub fn with_transform_3d(self, transform: Mat4) -> Self {
Layer::Transform3D {
transform,
layer: Box::new(self),
props: LayerProperties::new(),
}
}
pub fn with_clip(self, shape: ClipShape) -> Self {
Layer::Clip {
shape,
layer: Box::new(self),
props: LayerProperties::new(),
}
}
pub fn with_opacity(self, value: f32) -> Self {
Layer::Opacity {
value,
layer: Box::new(self),
props: LayerProperties::new(),
}
}
pub fn is_3d(&self) -> bool {
matches!(
self,
Layer::Scene3D { .. } | Layer::Billboard { .. } | Layer::Transform3D { .. }
)
}
pub fn is_2d(&self) -> bool {
matches!(
self,
Layer::Ui { .. }
| Layer::Canvas2D { .. }
| Layer::Transform2D { .. }
| Layer::Viewport3D { .. }
)
}
pub fn visit_children<F: FnMut(&Layer)>(&self, mut f: F) {
match self {
Layer::Stack { layers, .. } => {
for layer in layers {
f(layer);
}
}
Layer::Transform2D { layer, .. }
| Layer::Transform3D { layer, .. }
| Layer::Clip { layer, .. }
| Layer::Opacity { layer, .. }
| Layer::Offscreen { layer, .. }
| Layer::Billboard { layer, .. }
| Layer::Viewport3D { scene: layer, .. } => {
f(layer);
}
_ => {}
}
}
pub fn visit_children_mut<F: FnMut(&mut Layer)>(&mut self, mut f: F) {
match self {
Layer::Stack { layers, .. } => {
for layer in layers {
f(layer);
}
}
Layer::Transform2D { layer, .. }
| Layer::Transform3D { layer, .. }
| Layer::Clip { layer, .. }
| Layer::Opacity { layer, .. }
| Layer::Offscreen { layer, .. }
| Layer::Billboard { layer, .. }
| Layer::Viewport3D { scene: layer, .. } => {
f(layer);
}
_ => {}
}
}
}
#[derive(Debug, Default)]
pub struct SceneGraph {
pub root: Option<Layer>,
layer_index: HashMap<LayerId, usize>,
id_generator: LayerIdGenerator,
}
impl SceneGraph {
pub fn new() -> Self {
Self {
root: None,
layer_index: HashMap::new(),
id_generator: LayerIdGenerator::new(),
}
}
pub fn set_root(&mut self, layer: Layer) {
self.root = Some(layer);
self.rebuild_index();
}
pub fn new_layer_id(&mut self) -> LayerId {
self.id_generator.next_id()
}
pub fn find_layer(&self, id: LayerId) -> Option<&Layer> {
fn find_in_layer(layer: &Layer, target_id: LayerId) -> Option<&Layer> {
if layer.id() == Some(target_id) {
return Some(layer);
}
match layer {
Layer::Stack { layers, .. } => {
for child in layers {
if let Some(found) = find_in_layer(child, target_id) {
return Some(found);
}
}
}
Layer::Transform2D { layer: child, .. }
| Layer::Transform3D { layer: child, .. }
| Layer::Clip { layer: child, .. }
| Layer::Opacity { layer: child, .. }
| Layer::Offscreen { layer: child, .. }
| Layer::Billboard { layer: child, .. }
| Layer::Viewport3D { scene: child, .. } => {
if let Some(found) = find_in_layer(child, target_id) {
return Some(found);
}
}
_ => {}
}
None
}
self.root.as_ref().and_then(|root| find_in_layer(root, id))
}
fn rebuild_index(&mut self) {
self.layer_index.clear();
}
pub fn traverse<F: FnMut(&Layer, usize)>(&self, mut f: F) {
fn traverse_layer<F: FnMut(&Layer, usize)>(layer: &Layer, depth: usize, f: &mut F) {
f(layer, depth);
layer.visit_children(|child| traverse_layer(child, depth + 1, f));
}
if let Some(root) = &self.root {
traverse_layer(root, 0, &mut f);
}
}
pub fn layer_count(&self) -> usize {
let mut count = 0;
self.traverse(|_, _| count += 1);
count
}
pub fn has_3d(&self) -> bool {
let mut has_3d = false;
self.traverse(|layer, _| {
if layer.is_3d() {
has_3d = true;
}
});
has_3d
}
pub fn visible_layer_count(&self) -> usize {
let mut count = 0;
self.traverse(|layer, _| {
if layer.is_visible() {
count += 1;
}
});
count
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_layer_creation() {
let layer = Layer::empty();
assert!(layer.is_visible());
assert!(layer.id().is_none());
}
#[test]
fn test_layer_stack() {
let stack = Layer::stack(vec![Layer::empty(), Layer::empty(), Layer::empty()]);
let mut count = 0;
stack.visit_children(|_| count += 1);
assert_eq!(count, 3);
}
#[test]
fn test_layer_transforms() {
let layer = Layer::empty()
.with_transform_2d(Affine2D::translation(10.0, 20.0))
.with_opacity(0.5);
assert!(matches!(layer, Layer::Opacity { .. }));
}
#[test]
fn test_scene_graph() {
let mut scene = SceneGraph::new();
let id1 = scene.new_layer_id();
let id2 = scene.new_layer_id();
assert_ne!(id1, id2);
scene.set_root(Layer::stack(vec![
Layer::Empty {
props: LayerProperties::new().with_id(id1),
},
Layer::Empty {
props: LayerProperties::new().with_id(id2),
},
]));
assert_eq!(scene.layer_count(), 3);
let found = scene.find_layer(id1);
assert!(found.is_some());
}
#[test]
fn test_geometry_types() {
let p = Point::new(1.0, 2.0);
let s = Size::new(100.0, 50.0);
let r = Rect::from_origin_size(p, s);
assert_eq!(r.center(), Point::new(51.0, 27.0));
assert!(r.contains(Point::new(50.0, 25.0)));
assert!(!r.contains(Point::new(200.0, 100.0)));
let size = Size::new(200.0, 100.0);
let rect: Rect = size.into();
assert_eq!(rect.x(), 0.0);
assert_eq!(rect.y(), 0.0);
assert_eq!(rect.width(), 200.0);
assert_eq!(rect.height(), 100.0);
let rect2 = size.to_rect();
assert_eq!(rect, rect2);
let offset_rect = rect.offset(10.0, 20.0);
assert_eq!(offset_rect.x(), 10.0);
assert_eq!(offset_rect.y(), 20.0);
let inset_rect = rect.inset(5.0, 10.0);
assert_eq!(inset_rect.x(), 5.0);
assert_eq!(inset_rect.y(), 10.0);
assert_eq!(inset_rect.width(), 190.0);
assert_eq!(inset_rect.height(), 80.0);
}
#[test]
fn test_color() {
let c = Color::from_hex(0xFF5500);
assert_eq!(c.r, 1.0);
assert!((c.g - 85.0 / 255.0).abs() < 0.001);
assert_eq!(c.b, 0.0);
let c2 = c.with_alpha(0.5);
assert_eq!(c2.a, 0.5);
}
#[test]
fn test_mat4_operations() {
let t = Mat4::translation(1.0, 2.0, 3.0);
let s = Mat4::scale(2.0, 2.0, 2.0);
let result = t.mul(&s);
assert_eq!(result.cols[3][0], 1.0); }
}