use crate::engine::render::{FramePlan, PlanPlacement};
use crate::kernels::geometry::{js_round, Rect};
use crate::kernels::overscroll::{background_lines, page_edges, Edges, Lines, Pose, SIDES};
use std::collections::HashMap;
const SLACK: i64 = 3;
pub struct Resolved<'a> {
pub canvas: &'a str,
pub x: f64,
pub y: f64,
}
#[derive(Default)]
pub struct Overscroll {
plans: Vec<(u32, PlanPlacement)>,
edges: Option<HashMap<String, Edges>>,
last: HashMap<String, (String, f64, f64, [i64; 4])>,
}
impl Overscroll {
pub fn add_plan(&mut self, index: u32, plan: &FramePlan, moving: impl Fn(&str) -> bool) {
if self.edges.is_some() {
return;
}
for p in &plan.placements {
if p.skip != Some(true) && moving(&p.layer) {
self.plans.push((index, p.clone()));
}
}
}
pub fn prepare<E>(
&mut self,
mut resolve: impl FnMut(&PlanPlacement, u32) -> Result<Option<(String, f64, f64)>, E>,
extent: impl Fn(&str) -> Option<(i64, i64)>,
) -> Result<(), E> {
if self.edges.is_some() {
return Ok(());
}
let mut tracks: HashMap<(String, String), Vec<Pose>> = HashMap::new();
for (frame, p) in &self.plans {
if let Some((canvas, x, y)) = resolve(p, *frame)? {
tracks
.entry((canvas, p.layer.clone()))
.or_default()
.push(Pose {
frame: *frame,
x: js_round(x) as i64,
y: js_round(y) as i64,
});
}
}
let mut edges = HashMap::new();
for ((canvas, layer), mut track) in tracks {
let Some((w, h)) = extent(&layer) else {
continue;
};
track.sort_by_key(|p| p.frame);
let found = page_edges(&track, w, h);
if !found.is_empty() {
edges.insert(canvas, found);
}
}
self.plans = Vec::new();
self.edges = Some(edges);
Ok(())
}
pub fn measure(&mut self, layer: &str, at: Resolved, lines: &Lines) -> [i64; 4] {
let mut out = [0; 4];
let edges = self.edges.as_ref().and_then(|e| e.get(at.canvas));
if let Some(edges) = edges {
let over = edges.overshoot(js_round(at.x) as i64, js_round(at.y) as i64);
for (side, (&over, out)) in SIDES.iter().zip(over.iter().zip(out.iter_mut())) {
if over <= 0 {
continue;
}
let counted = background_lines(lines, *side, over as usize) as i64;
if counted + SLACK >= over {
*out = counted;
}
}
}
self.last
.insert(layer.to_string(), (at.canvas.to_string(), at.x, at.y, out));
out
}
pub fn reuse(&self, layer: &str, at: Resolved) -> [i64; 4] {
match self.last.get(layer) {
Some((canvas, x, y, out)) if canvas == at.canvas && *x == at.x && *y == at.y => *out,
_ => [0; 4],
}
}
}
pub fn pixel_rect(region: Rect) -> (i64, i64, i64, i64) {
let (x0, y0) = (region.x.floor() as i64, region.y.floor() as i64);
let (x1, y1) = (
(region.x + region.width).ceil() as i64,
(region.y + region.height).ceil() as i64,
);
(x0, y0, x1 - x0, y1 - y0)
}
pub fn strips(region: Rect, out: [i64; 4]) -> Vec<Rect> {
let (x, y, w, h) = pixel_rect(region);
let [top, bottom, left, right] = out;
let rect = |x: i64, y: i64, w: i64, h: i64| Rect {
x: x as f64,
y: y as f64,
width: w as f64,
height: h as f64,
};
let mut rects = Vec::new();
if top > 0 {
rects.push(rect(x, y, w, top));
}
if bottom > 0 {
rects.push(rect(x, y + h - bottom, w, bottom));
}
if left > 0 {
rects.push(rect(x, y, left, h));
}
if right > 0 {
rects.push(rect(x + w - right, y, right, h));
}
rects
}
pub fn trim(bounds: Rect, out: [i64; 4]) -> Rect {
let [top, bottom, left, right] = out.map(|v| v as f64);
let width = bounds.width - left - right;
let height = bounds.height - top - bottom;
if width <= 0.0 || height <= 0.0 {
return Rect {
x: 0.0,
y: 0.0,
width: 0.0,
height: 0.0,
};
}
Rect {
x: bounds.x + left,
y: bounds.y + top,
width,
height,
}
}