use crate::geometry::{Constraints, Rect, Vec2};
use crate::layer::{render_vector_children, vector_children_bounds};
use crate::vector::{VectorComponent, VectorGraphic};
#[crate::component(vector)]
#[derive(Clone, PartialEq, Hash)]
pub struct Fragment {
#[children(each = child)]
pub children: Vec<Box<dyn VectorComponent>>,
}
impl Fragment {
pub fn empty() -> Self {
Self {
children: Vec::new(),
}
}
pub fn single(child: impl Into<Box<dyn VectorComponent>>) -> Self {
Self {
children: vec![child.into()],
}
}
}
impl<T: Into<Box<dyn VectorComponent>>> FromIterator<T> for Fragment {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Self {
children: iter.into_iter().map(Into::into).collect(),
}
}
}
impl VectorComponent for Fragment {
fn layout(&self, constraints: Constraints) -> Vec2 {
constraints.constrain(vector_children_bounds(&self.children).size)
}
fn paint_bounds(&self, _size: Vec2) -> Rect {
vector_children_bounds(&self.children)
}
fn render(&self, size: Vec2) -> VectorGraphic {
let paint_bounds = self.paint_bounds(size);
render_vector_children(&self.children, paint_bounds, Constraints::loose(size))
}
}
pub mod raster {
use crate::geometry::{Constraints, Rect, Vec2};
use crate::layer::{composite_children, raster_children_bounds};
use crate::raster::{RasterComponent, RasterImage, RasterResidency, Resolution};
use crate::render_context::RenderContext;
#[crate::component(raster)]
#[derive(Clone, PartialEq, Hash)]
pub struct Fragment {
#[children(each = child)]
pub children: Vec<Box<dyn RasterComponent>>,
}
impl Fragment {
pub fn empty() -> Self {
Self {
children: Vec::new(),
}
}
pub fn single(child: impl Into<Box<dyn RasterComponent>>) -> Self {
Self {
children: vec![child.into()],
}
}
}
impl<T: Into<Box<dyn RasterComponent>>> FromIterator<T> for Fragment {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Self {
children: iter.into_iter().map(Into::into).collect(),
}
}
}
impl RasterComponent for Fragment {
fn layout(&self, constraints: Constraints) -> Vec2 {
constraints.constrain(raster_children_bounds(&self.children).size)
}
fn paint_bounds(&self, _size: Vec2) -> Rect {
raster_children_bounds(&self.children)
}
fn render(
&self,
size: Vec2,
target: Resolution,
residency: RasterResidency,
ctx: &mut dyn RenderContext,
) -> RasterImage {
let paint_rect = raster_children_bounds(&self.children);
let child_constraints = Constraints::loose(size);
let placed: Vec<(Vec2, Vec2, &dyn RasterComponent)> = self
.children
.iter()
.map(|child| {
let child_size = child.layout(child_constraints);
(Vec2::ZERO, child_size, child.as_ref())
})
.collect();
composite_children(paint_rect, target, &placed, residency, ctx)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color::Color;
use crate::placement::VectorPlacement;
use crate::shapes::Rectangle;
use crate::vector::Paint;
fn rect(w: f32, h: f32) -> Rectangle {
Rectangle {
size: Vec2(w, h),
fill: Paint::Solid(Color::rgb_u8(0, 0, 0)).into(),
stroke: None,
}
}
#[test]
fn fragment_fits_single_child_size() {
let fragment = Fragment {
children: vec![rect(80.0, 40.0).place_at(Vec2(10.0, 20.0)).into()],
};
assert_eq!(fragment.layout(Constraints::UNBOUNDED), Vec2(80.0, 40.0));
}
#[test]
fn fragment_unions_disjoint_children() {
let fragment = Fragment {
children: vec![
rect(50.0, 50.0).place_at(Vec2(0.0, 0.0)).into(),
rect(50.0, 50.0).place_at(Vec2(100.0, 100.0)).into(),
],
};
assert_eq!(fragment.layout(Constraints::UNBOUNDED), Vec2(150.0, 150.0));
}
#[test]
fn fragment_handles_negative_positions() {
let fragment = Fragment {
children: vec![
rect(100.0, 100.0).place_at(Vec2(-30.0, 0.0)).into(),
rect(100.0, 100.0).place_at(Vec2(50.0, 20.0)).into(),
],
};
assert_eq!(fragment.layout(Constraints::UNBOUNDED), Vec2(180.0, 120.0));
}
#[test]
fn fragment_empty_is_zero() {
let fragment = Fragment::empty();
assert_eq!(fragment.layout(Constraints::UNBOUNDED), Vec2::ZERO);
}
#[test]
fn fragment_view_box_matches_paint_bounds_when_children_spill() {
let fragment = Fragment {
children: vec![
rect(100.0, 60.0).place_at(Vec2::ZERO).into(),
rect(100.0, 60.0).place_at(Vec2(13.0, 9.0)).into(),
],
};
let size = fragment.layout(Constraints::tight(Vec2(100.0, 60.0)));
let paint_bounds = Rect {
origin: Vec2::ZERO,
size: Vec2(113.0, 69.0),
};
assert_eq!(size, Vec2(100.0, 60.0));
assert_eq!(fragment.paint_bounds(size), paint_bounds);
assert_eq!(fragment.render(size).view_box, paint_bounds);
}
}