use kurbo::{BezPath, Rect as KRect, Shape, Vec2};
use rstar::{AABB, RTree, RTreeObject};
use crate::render::Draw;
use super::*;
pub struct Command {
pub draw: Draw,
pub clip: Id,
pub effect: Option<Id>,
pub bounds: KRect,
}
pub struct Builder<'a> {
pub(super) trees: &'a mut Trees,
pub(super) transform: Id,
pub(super) stack: Vec<(Id, Option<Id>)>,
pub(super) commands: Vec<Command>,
pub(super) made: Made,
}
#[derive(Default, Clone)]
pub struct Made {
pub clips: Vec<Id>,
pub effects: Vec<Id>,
}
impl<'a> Builder<'a> {
pub fn new(trees: &'a mut Trees, transform: Id, clip: Id, effect: Option<Id>) -> Builder<'a> {
Builder {
trees,
transform,
stack: vec![(clip, effect)],
commands: Vec::new(),
made: Made::default(),
}
}
pub fn draw(&mut self, draw: Draw) {
let bounds = match &draw {
Draw::Fill(path, _) => path.bounding_box(),
Draw::Stroke(path, width, _) => {
let half = *width as f64 / 2.0;
path.bounding_box().inflate(half, half)
}
Draw::Glyphs(_, size, _, _, glyphs) => glyph_bounds(*size, glyphs),
};
let (clip, effect) = *self.stack.last().expect("balanced layers");
self.commands.push(Command {
draw,
clip,
effect,
bounds: bounds.inflate(1.0, 1.0),
});
}
pub fn push_clip(&mut self, path: BezPath) {
let (clip, effect) = *self.stack.last().expect("balanced layers");
let id = self.trees.clips.add(Clip {
parent: Some(clip),
transform: self.transform,
path,
});
self.made.clips.push(id);
self.stack.push((id, effect));
}
pub fn push_layer(&mut self, blend: Blend, bounds: Option<BezPath>) {
let (clip, effect) = *self.stack.last().expect("balanced layers");
let id = self.trees.effects.add(Effect {
parent: effect,
clip,
transform: self.transform,
blend,
bounds,
});
self.made.effects.push(id);
self.stack.push((clip, Some(id)));
}
pub fn pop(&mut self) {
if self.stack.len() > 1 {
self.stack.pop();
}
}
pub fn finish(self) -> (Vec<Command>, Made) {
(self.commands, self.made)
}
}
pub(super) fn glyph_bounds(size: f32, glyphs: &[crate::render::Glyph]) -> KRect {
let mut r: Option<KRect> = None;
for g in glyphs {
let p = KRect::new(g.x as f64, g.y as f64, g.x as f64, g.y as f64);
r = Some(r.map_or(p, |r| r.union(p)));
}
let em = size as f64;
r.unwrap_or_default().inflate(em, em)
}
pub(super) struct Local {
pub(super) at: usize,
pub(super) bounds: KRect,
}
impl RTreeObject for Local {
type Envelope = AABB<[f64; 2]>;
fn envelope(&self) -> Self::Envelope {
aabb(self.bounds)
}
}
pub(super) fn aabb(r: KRect) -> AABB<[f64; 2]> {
AABB::from_corners([r.x0, r.y0], [r.x1, r.y1])
}
pub use crate::tree::Order;
#[derive(Clone, Debug, PartialEq)]
pub struct Hit {
pub action: String,
pub area: KRect,
}
pub struct Chunk {
pub transform: Id,
pub space: Space,
pub origin: Vec2,
pub commands: Vec<Command>,
pub(super) index: RTree<Local>,
pub cut: Option<KRect>,
pub covers: Vec<KRect>,
pub bounds: Option<KRect>,
pub action: Option<(String, KRect)>,
pub made: Made,
}
impl Chunk {
#[allow(clippy::too_many_arguments)]
pub fn new(
transform: Id,
space: Space,
origin: Vec2,
(commands, made): (Vec<Command>, Made),
covers: Vec<KRect>,
action: Option<(String, KRect)>,
cut: Option<KRect>,
) -> Chunk {
let index = RTree::bulk_load(
commands
.iter()
.enumerate()
.map(|(at, c)| Local {
at,
bounds: c.bounds,
})
.collect(),
);
let mut chunk = Chunk {
transform,
space,
origin,
commands,
index,
cut,
covers,
bounds: None,
action,
made,
};
chunk.place(origin, cut);
chunk
}
pub fn moved(mut self, origin: Vec2, cut: Option<KRect>) -> Chunk {
self.place(origin, cut);
self
}
pub(super) fn place(&mut self, origin: Vec2, cut: Option<KRect>) {
let local = self
.commands
.iter()
.map(|c| c.bounds)
.reduce(|a, b| a.union(b));
self.bounds = local.and_then(|r| cut_to(r + origin, cut));
self.origin = origin;
self.cut = cut;
}
pub fn cover(&self) -> Option<KRect> {
self.covers
.iter()
.filter_map(|&r| cut_to(r + self.origin, self.cut))
.reduce(|a, b| a.union(b))
}
pub(super) fn reach(&self) -> Option<KRect> {
let hit = self
.hit()
.map(|h| h.area)
.filter(|r| r.width() > 0.0 && r.height() > 0.0);
match (self.bounds, hit) {
(Some(a), Some(b)) => Some(a.union(b)),
(a, b) => a.or(b),
}
}
}
pub(super) fn cut_to(r: KRect, cut: Option<KRect>) -> Option<KRect> {
let r = cut.map_or(r, |c| r.intersect(c));
(r.width() > 0.0 && r.height() > 0.0).then_some(r)
}