//! Track and compute the damage of graphical primitives. use crate::core::{Rectangle, Size}; use crate::Primitive; use std::sync::Arc; /// Computes the damage regions between the two given primitives. pub fn regions(a: &Primitive, b: &Primitive) -> Vec { match (a, b) { ( Primitive::Group { primitives: primitives_a, }, Primitive::Group { primitives: primitives_b, }, ) => return list(primitives_a, primitives_b), ( Primitive::Clip { bounds: bounds_a, content: content_a, .. }, Primitive::Clip { bounds: bounds_b, content: content_b, .. }, ) => { if bounds_a == bounds_b { return regions(content_a, content_b) .into_iter() .filter_map(|r| r.intersection(&bounds_a.expand(1.0))) .collect(); } else { return vec![bounds_a.expand(1.0), bounds_b.expand(1.0)]; } } ( Primitive::Translate { translation: translation_a, content: content_a, }, Primitive::Translate { translation: translation_b, content: content_b, }, ) => { if translation_a == translation_b { return regions(content_a, content_b) .into_iter() .map(|r| r + *translation_a) .collect(); } } ( Primitive::Cache { content: content_a }, Primitive::Cache { content: content_b }, ) => { if Arc::ptr_eq(content_a, content_b) { return vec![]; } } _ if a == b => return vec![], _ => {} } let bounds_a = a.bounds(); let bounds_b = b.bounds(); if bounds_a == bounds_b { vec![bounds_a] } else { vec![bounds_a, bounds_b] } } /// Computes the damage regions between the two given lists of primitives. pub fn list(previous: &[Primitive], current: &[Primitive]) -> Vec { let damage = previous .iter() .zip(current) .flat_map(|(a, b)| regions(a, b)); if previous.len() == current.len() { damage.collect() } else { let (smaller, bigger) = if previous.len() < current.len() { (previous, current) } else { (current, previous) }; // Extend damage by the added/removed primitives damage .chain(bigger[smaller.len()..].iter().map(Primitive::bounds)) .collect() } } /// Groups the given damage regions that are close together inside the given /// bounds. pub fn group( mut damage: Vec, scale_factor: f32, bounds: Size, ) -> Vec { use std::cmp::Ordering; const AREA_THRESHOLD: f32 = 20_000.0; let bounds = Rectangle { x: 0.0, y: 0.0, width: bounds.width as f32, height: bounds.height as f32, }; damage.sort_by(|a, b| { a.x.partial_cmp(&b.x) .unwrap_or(Ordering::Equal) .then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal)) }); let mut output = Vec::new(); let mut scaled = damage .into_iter() .filter_map(|region| (region * scale_factor).intersection(&bounds)) .filter(|region| region.width >= 1.0 && region.height >= 1.0); if let Some(mut current) = scaled.next() { for region in scaled { let union = current.union(®ion); if union.area() - current.area() - region.area() <= AREA_THRESHOLD { current = union; } else { output.push(current); current = region; } } output.push(current); } output }