use crate::geometry::{Anchor, Constraints, Rect, Transform, Vec2};
use crate::vector::{Group, Node, VectorComponent, VectorGraphic};
use crate::Keyable;
#[derive(Keyable)]
pub struct Positioned {
pub offset: Vec2,
pub child: Box<dyn VectorComponent>,
}
impl Positioned {
pub fn new(offset: Vec2, child: impl Into<Box<dyn VectorComponent>>) -> Self {
Self {
offset,
child: child.into(),
}
}
}
impl VectorComponent for Positioned {
fn layout(&self, _constraints: Constraints) -> Vec2 {
self.child.layout(Constraints::UNBOUNDED)
}
fn paint_bounds(&self, size: Vec2) -> Rect {
let bounds = self.child.paint_bounds(size);
Rect {
origin: bounds.origin + self.offset,
size: bounds.size,
}
}
fn render(&self, size: Vec2) -> VectorGraphic {
let inner = self.child.render(size);
VectorGraphic {
view_box: Rect {
origin: inner.view_box.origin + self.offset,
size: inner.view_box.size,
},
root: Node::Group(Group {
transform: Transform::translate(self.offset),
opacity: 1.0,
children: vec![inner.root],
}),
}
}
}
impl From<Positioned> for Box<dyn VectorComponent> {
fn from(positioned: Positioned) -> Self {
Box::new(positioned)
}
}
pub trait VectorPlacement: VectorComponent + Sized + 'static {
fn place_at(self, position: Vec2) -> Positioned {
Positioned {
offset: position,
child: Box::new(self),
}
}
fn anchored(self, anchor: Anchor) -> AnchoredVectorComponent<Self> {
AnchoredVectorComponent {
component: self,
anchor,
}
}
}
impl<T: VectorComponent + 'static> VectorPlacement for T {}
pub struct AnchoredVectorComponent<C: VectorComponent> {
component: C,
anchor: Anchor,
}
impl<C: VectorComponent + 'static> AnchoredVectorComponent<C> {
pub fn snap_to(self, target_point: Vec2) -> Positioned {
let intrinsic = self.component.layout(Constraints::UNBOUNDED);
let offset = intrinsic.anchored(self.anchor).snap_to(target_point);
Positioned {
offset,
child: Box::new(self.component),
}
}
}
pub mod raster {
use crate::geometry::{Anchor, Constraints, Rect, Vec2};
use crate::raster::{RasterComponent, RasterImage, Resolution};
use crate::render_context::{CachePolicy, RenderContext};
use crate::Keyable;
#[derive(Keyable)]
pub struct Positioned {
pub offset: Vec2,
pub child: Box<dyn RasterComponent>,
}
impl Positioned {
pub fn new(offset: Vec2, child: impl Into<Box<dyn RasterComponent>>) -> Self {
Self {
offset,
child: child.into(),
}
}
}
impl RasterComponent for Positioned {
fn layout(&self, _constraints: Constraints) -> Vec2 {
self.child.layout(Constraints::UNBOUNDED)
}
fn paint_bounds(&self, size: Vec2) -> Rect {
let bounds = self.child.paint_bounds(size);
Rect {
origin: bounds.origin + self.offset,
size: bounds.size,
}
}
fn cache_policy(&self) -> CachePolicy {
CachePolicy::Transparent
}
fn render(
&self,
size: Vec2,
target: Resolution,
ctx: &mut dyn RenderContext,
) -> RasterImage {
ctx.render(self.child.as_ref(), size, target)
}
}
impl From<Positioned> for Box<dyn RasterComponent> {
fn from(positioned: Positioned) -> Self {
Box::new(positioned)
}
}
pub trait RasterPlacement: RasterComponent + Sized + 'static {
fn place_at(self, position: Vec2) -> Positioned {
Positioned {
offset: position,
child: Box::new(self),
}
}
fn anchored(self, anchor: Anchor) -> AnchoredRasterComponent<Self> {
AnchoredRasterComponent {
component: self,
anchor,
}
}
}
impl<T: RasterComponent + 'static> RasterPlacement for T {}
pub struct AnchoredRasterComponent<C: RasterComponent> {
component: C,
anchor: Anchor,
}
impl<C: RasterComponent + 'static> AnchoredRasterComponent<C> {
pub fn snap_to(self, target_point: Vec2) -> Positioned {
let intrinsic = self.component.layout(Constraints::UNBOUNDED);
let offset = intrinsic.anchored(self.anchor).snap_to(target_point);
Positioned {
offset,
child: Box::new(self.component),
}
}
}
}
pub use raster::RasterPlacement;
#[cfg(test)]
mod tests {
use super::raster::RasterPlacement;
use super::*;
use crate::color::Color;
use crate::raster::{PixelFormat, RasterComponent, RasterImage, Resolution};
use crate::render_context::RenderContext;
use crate::shapes::Circle;
use crate::vector::Paint;
fn big_circle() -> Circle {
Circle::builder()
.radius(720.0)
.fill(Paint::Solid(Color::rgb_u8(0, 0, 0)))
.build()
}
#[test]
fn placed_child_is_measured_unbounded() {
let placed = big_circle().place_at(Vec2::ZERO);
let size = placed.layout(Constraints::loose(Vec2(1920.0, 1080.0)));
assert_eq!(size, Vec2(1440.0, 1440.0));
}
#[test]
fn snap_offset_and_layout_agree_for_oversized_children() {
let placed = big_circle()
.anchored(Anchor::CENTER)
.snap_to(Vec2(960.0, 540.0));
assert_eq!(placed.offset, Vec2(240.0, -180.0));
let size = placed.layout(Constraints::loose(Vec2(1920.0, 1080.0)));
assert_eq!(size, Vec2(1440.0, 1440.0));
}
#[derive(PartialEq, Hash)]
struct FixedRaster;
impl RasterComponent for FixedRaster {
fn layout(&self, constraints: Constraints) -> Vec2 {
constraints.constrain(Vec2(2000.0, 2000.0))
}
fn render(&self, _s: Vec2, t: Resolution, _ctx: &mut dyn RenderContext) -> RasterImage {
let bytes = (t.width as usize) * (t.height as usize) * 4;
RasterImage::cpu(t.width, t.height, PixelFormat::Rgba8, vec![0; bytes])
}
}
#[test]
fn raster_placed_child_is_measured_unbounded() {
let placed = FixedRaster.place_at(Vec2::ZERO);
let size = placed.layout(Constraints::loose(Vec2(100.0, 100.0)));
assert_eq!(size, Vec2(2000.0, 2000.0));
}
}