use crate::geometry::Color;
use crate::text::TextLayout;
use crate::view::paint::ImageFit;
pub use kurbo::{Affine, BezPath, Point as KPoint, Rect as KRect, Vec2};
use std::sync::Arc;
pub type TextLayoutRef = Arc<TextLayout>;
#[derive(Debug, Clone, Copy, Default)]
pub struct Transform {
pub translate: Vec2,
pub rotate: f64,
pub scale: Vec2,
}
impl Transform {
pub fn new() -> Self {
Self::default()
}
pub fn translate(x: f64, y: f64) -> Self {
Self {
translate: Vec2::new(x, y),
..Default::default()
}
}
pub fn to_affine(&self) -> Affine {
Affine::translate(self.translate)
* Affine::rotate(self.rotate)
* Affine::scale_non_uniform(self.scale.x, self.scale.y)
}
}
#[derive(Debug, Clone, Default)]
pub struct FillStrokeStyle {
pub fill: Option<Color>,
pub stroke: Option<Stroke>,
}
impl FillStrokeStyle {
pub fn new() -> Self {
Self::default()
}
pub fn with_fill(mut self, c: Color) -> Self {
self.fill = Some(c);
self
}
pub fn with_stroke(mut self, c: Color, w: f64) -> Self {
self.stroke = Some(Stroke::new(c, w));
self
}
}
#[derive(Debug, Clone)]
pub struct Stroke {
pub color: Color,
pub width: f64,
}
impl Stroke {
pub fn new(c: Color, w: f64) -> Self {
Self { color: c, width: w }
}
}
impl Default for Stroke {
fn default() -> Self {
Self {
color: Color::BLACK,
width: 1.0,
}
}
}
#[derive(Debug, Clone)]
pub struct LayeredElement {
pub element: VisualElement,
pub z_index: i32,
pub element_id: Option<u64>,
}
impl LayeredElement {
pub fn new(e: VisualElement, z: i32) -> Self {
Self {
element: e,
z_index: z,
element_id: None,
}
}
pub fn with_id(mut self, id: u64) -> Self {
self.element_id = Some(id);
self
}
pub fn z_index(&self) -> i32 {
self.z_index
}
}
pub enum VisualElement {
Rect {
rect: KRect,
style: FillStrokeStyle,
},
RoundedRect {
rect: KRect,
radius: f64,
style: FillStrokeStyle,
},
ShadowRoundedRect {
rect: KRect,
radius: f64,
std_dev: f64,
color: Color,
},
Circle {
center: KPoint,
radius: f64,
style: FillStrokeStyle,
},
Line {
start: KPoint,
end: KPoint,
style: Stroke,
},
Path {
path: BezPath,
style: FillStrokeStyle,
},
TextRun {
text: Arc<str>,
position: KPoint,
color: Color,
font_size: f64,
font_family: String,
rotation: f64,
max_width: Option<f64>,
layout: Option<TextLayoutRef>,
},
Image {
bounds: KRect,
data: Arc<Vec<u8>>,
width: u32,
height: u32,
opacity: Option<f32>,
fit: ImageFit,
border_radius: f32,
},
Group {
children: Vec<LayeredElement>,
transform: Option<Transform>,
clip_rect: Option<KRect>,
},
}
impl VisualElement {
pub fn bounding_rect(&self) -> Option<KRect> {
match self {
Self::Rect { rect, .. } => Some(*rect),
Self::RoundedRect { rect, .. } => Some(*rect),
Self::ShadowRoundedRect { rect, .. } => Some(*rect),
Self::Circle { center, radius, .. } => Some(KRect::new(
center.x - radius,
center.y - radius,
center.x + radius,
center.y + radius,
)),
Self::Line { start, end, .. } => Some(KRect::new(
start.x.min(end.x),
start.y.min(end.y),
start.x.max(end.x),
start.y.max(end.y),
)),
Self::Path { .. } => None, Self::TextRun {
position,
font_size,
..
} => {
Some(KRect::new(
position.x,
position.y,
position.x + 2000.0, position.y + font_size,
))
}
Self::Image { bounds, .. } => Some(*bounds),
Self::Group { .. } => None,
}
}
}
impl std::fmt::Debug for VisualElement {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Rect { rect, style } => f
.debug_struct("Rect")
.field("rect", rect)
.field("style", style)
.finish(),
Self::RoundedRect { rect, radius, .. } => f
.debug_struct("RoundedRect")
.field("rect", rect)
.field("radius", radius)
.finish(),
Self::ShadowRoundedRect {
rect,
radius,
std_dev,
..
} => f
.debug_struct("ShadowRoundedRect")
.field("rect", rect)
.field("radius", radius)
.field("std_dev", std_dev)
.finish(),
Self::TextRun {
text,
color,
font_size,
..
} => f
.debug_struct("TextRun")
.field("text", text)
.field("color", color)
.field("font_size", font_size)
.finish(),
Self::Group {
children,
transform,
clip_rect: _,
} => f
.debug_struct("Group")
.field("children", &children.len())
.field("transform", transform)
.finish(),
_ => write!(f, "{:?}", std::mem::discriminant(self)),
}
}
}
impl Clone for VisualElement {
fn clone(&self) -> Self {
match self {
Self::Rect { rect, style } => Self::Rect {
rect: *rect,
style: style.clone(),
},
Self::RoundedRect {
rect,
radius,
style,
..
} => Self::RoundedRect {
rect: *rect,
radius: *radius,
style: style.clone(),
},
Self::ShadowRoundedRect {
rect,
radius,
std_dev,
color,
} => Self::ShadowRoundedRect {
rect: *rect,
radius: *radius,
std_dev: *std_dev,
color: *color,
},
Self::Circle {
center,
radius,
style,
} => Self::Circle {
center: *center,
radius: *radius,
style: style.clone(),
},
Self::Line { start, end, style } => Self::Line {
start: *start,
end: *end,
style: style.clone(),
},
Self::Path { path, style } => Self::Path {
path: path.clone(),
style: style.clone(),
},
Self::TextRun {
text,
position,
color,
font_size,
font_family,
rotation,
max_width,
layout,
} => Self::TextRun {
text: text.clone(),
position: *position,
color: *color,
font_size: *font_size,
font_family: font_family.clone(),
rotation: *rotation,
max_width: *max_width,
layout: layout.clone(),
},
Self::Image {
bounds,
data,
width,
height,
opacity,
fit,
border_radius,
} => Self::Image {
bounds: *bounds,
data: data.clone(),
width: *width,
height: *height,
opacity: *opacity,
fit: *fit,
border_radius: *border_radius,
},
Self::Group {
children,
transform,
clip_rect,
} => Self::Group {
children: children.clone(),
transform: *transform,
clip_rect: *clip_rect,
},
}
}
}