use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Color {
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Self::rgba(r, g, b, 255)
}
pub const TRANSPARENT: Self = Self::rgba(0, 0, 0, 0);
pub const BLACK: Self = Self::rgb(0, 0, 0);
pub const WHITE: Self = Self::rgb(255, 255, 255);
pub fn from_hex(hex: &str) -> Option<Self> {
let s = hex.trim_start_matches('#');
match s.len() {
6 => {
let r = u8::from_str_radix(&s[0..2], 16).ok()?;
let g = u8::from_str_radix(&s[2..4], 16).ok()?;
let b = u8::from_str_radix(&s[4..6], 16).ok()?;
Some(Self::rgb(r, g, b))
}
8 => {
let r = u8::from_str_radix(&s[0..2], 16).ok()?;
let g = u8::from_str_radix(&s[2..4], 16).ok()?;
let b = u8::from_str_radix(&s[4..6], 16).ok()?;
let a = u8::from_str_radix(&s[6..8], 16).ok()?;
Some(Self::rgba(r, g, b, a))
}
_ => None,
}
}
pub fn with_opacity(mut self, opacity: f32) -> Self {
self.a = (self.a as f32 * opacity.clamp(0.0, 1.0)) as u8;
self
}
pub fn to_tiny_skia_color(self) -> tiny_skia::Color {
tiny_skia::Color::from_rgba8(self.r, self.g, self.b, self.a)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GradientStop {
pub position: f32, pub color: Color,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SceneNode {
Rect {
x: f32,
y: f32,
w: f32,
h: f32,
fill: Color,
stroke: Option<Color>,
stroke_width: f32,
corner_radius: f32,
},
Circle {
cx: f32,
cy: f32,
r: f32,
fill: Color,
stroke: Option<Color>,
stroke_width: f32,
},
Path {
d: String,
fill: Option<Color>,
stroke: Option<Color>,
stroke_width: f32,
opacity: f32,
},
Text {
x: f32,
y: f32,
content: String,
font_size: f32,
color: Color,
font_weight: u16, },
LinearGradient {
x: f32,
y: f32,
w: f32,
h: f32,
angle_deg: f32,
stops: Vec<GradientStop>,
},
RadialGradient {
cx: f32,
cy: f32,
r: f32,
stops: Vec<GradientStop>,
},
Group {
transform: Transform2D,
opacity: f32,
children: Vec<SceneNode>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Transform2D {
pub tx: f32,
pub ty: f32,
pub scale_x: f32,
pub scale_y: f32,
pub rotate_deg: f32,
}
impl Default for Transform2D {
fn default() -> Self {
Self {
tx: 0.0,
ty: 0.0,
scale_x: 1.0,
scale_y: 1.0,
rotate_deg: 0.0,
}
}
}
impl Transform2D {
pub fn to_tiny_skia(&self) -> tiny_skia::Transform {
tiny_skia::Transform::from_translate(self.tx, self.ty)
.post_scale(self.scale_x, self.scale_y)
.post_rotate(self.rotate_deg)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Scene {
pub nodes: Vec<SceneNode>,
}
impl Scene {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, node: SceneNode) {
self.nodes.push(node);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_color_from_hex() {
let c = Color::from_hex("#ff6600").unwrap();
assert_eq!(c.r, 0xff);
assert_eq!(c.g, 0x66);
assert_eq!(c.b, 0x00);
assert_eq!(c.a, 0xff);
}
#[test]
fn test_scene_push() {
let mut scene = Scene::new();
scene.push(SceneNode::Rect {
x: 0.0,
y: 0.0,
w: 100.0,
h: 100.0,
fill: Color::rgb(255, 0, 0),
stroke: None,
stroke_width: 0.0,
corner_radius: 0.0,
});
assert_eq!(scene.nodes.len(), 1);
}
}