1use crate::engine::render::{FramePlan, PlanPlacement};
10use crate::kernels::geometry::{js_round, Rect};
11use crate::kernels::overscroll::{background_lines, page_edges, Edges, Lines, Pose, SIDES};
12use std::collections::HashMap;
13
14const SLACK: i64 = 3;
17
18pub struct Resolved<'a> {
20 pub canvas: &'a str,
21 pub x: f64,
22 pub y: f64,
23}
24
25#[derive(Default)]
26pub struct Overscroll {
27 plans: Vec<(u32, PlanPlacement)>,
29 edges: Option<HashMap<String, Edges>>,
31 last: HashMap<String, (String, f64, f64, [i64; 4])>,
33}
34
35impl Overscroll {
36 pub fn add_plan(&mut self, index: u32, plan: &FramePlan, moving: impl Fn(&str) -> bool) {
38 if self.edges.is_some() {
39 return;
40 }
41 for p in &plan.placements {
42 if p.skip != Some(true) && moving(&p.layer) {
43 self.plans.push((index, p.clone()));
44 }
45 }
46 }
47
48 pub fn prepare<E>(
51 &mut self,
52 mut resolve: impl FnMut(&PlanPlacement, u32) -> Result<Option<(String, f64, f64)>, E>,
53 extent: impl Fn(&str) -> Option<(i64, i64)>,
54 ) -> Result<(), E> {
55 if self.edges.is_some() {
56 return Ok(());
57 }
58 let mut tracks: HashMap<(String, String), Vec<Pose>> = HashMap::new();
59 for (frame, p) in &self.plans {
60 if let Some((canvas, x, y)) = resolve(p, *frame)? {
61 tracks
62 .entry((canvas, p.layer.clone()))
63 .or_default()
64 .push(Pose {
65 frame: *frame,
66 x: js_round(x) as i64,
67 y: js_round(y) as i64,
68 });
69 }
70 }
71 let mut edges = HashMap::new();
72 for ((canvas, layer), mut track) in tracks {
73 let Some((w, h)) = extent(&layer) else {
74 continue;
75 };
76 track.sort_by_key(|p| p.frame);
77 let found = page_edges(&track, w, h);
78 if !found.is_empty() {
79 edges.insert(canvas, found);
80 }
81 }
82 self.plans = Vec::new();
83 self.edges = Some(edges);
84 Ok(())
85 }
86
87 pub fn measure(&mut self, layer: &str, at: Resolved, lines: &Lines) -> [i64; 4] {
94 let mut out = [0; 4];
95 let edges = self.edges.as_ref().and_then(|e| e.get(at.canvas));
96 if let Some(edges) = edges {
97 let over = edges.overshoot(js_round(at.x) as i64, js_round(at.y) as i64);
98 for (side, (&over, out)) in SIDES.iter().zip(over.iter().zip(out.iter_mut())) {
99 if over <= 0 {
100 continue;
101 }
102 let counted = background_lines(lines, *side, over as usize) as i64;
103 if counted + SLACK >= over {
104 *out = counted;
105 }
106 }
107 }
108 self.last
109 .insert(layer.to_string(), (at.canvas.to_string(), at.x, at.y, out));
110 out
111 }
112
113 pub fn reuse(&self, layer: &str, at: Resolved) -> [i64; 4] {
116 match self.last.get(layer) {
117 Some((canvas, x, y, out)) if canvas == at.canvas && *x == at.x && *y == at.y => *out,
118 _ => [0; 4],
119 }
120 }
121}
122
123pub fn pixel_rect(region: Rect) -> (i64, i64, i64, i64) {
125 let (x0, y0) = (region.x.floor() as i64, region.y.floor() as i64);
126 let (x1, y1) = (
127 (region.x + region.width).ceil() as i64,
128 (region.y + region.height).ceil() as i64,
129 );
130 (x0, y0, x1 - x0, y1 - y0)
131}
132
133pub fn strips(region: Rect, out: [i64; 4]) -> Vec<Rect> {
135 let (x, y, w, h) = pixel_rect(region);
136 let [top, bottom, left, right] = out;
137 let rect = |x: i64, y: i64, w: i64, h: i64| Rect {
138 x: x as f64,
139 y: y as f64,
140 width: w as f64,
141 height: h as f64,
142 };
143 let mut rects = Vec::new();
144 if top > 0 {
145 rects.push(rect(x, y, w, top));
146 }
147 if bottom > 0 {
148 rects.push(rect(x, y + h - bottom, w, bottom));
149 }
150 if left > 0 {
151 rects.push(rect(x, y, left, h));
152 }
153 if right > 0 {
154 rects.push(rect(x + w - right, y, right, h));
155 }
156 rects
157}
158
159pub fn trim(bounds: Rect, out: [i64; 4]) -> Rect {
161 let [top, bottom, left, right] = out.map(|v| v as f64);
162 let width = bounds.width - left - right;
163 let height = bounds.height - top - bottom;
164 if width <= 0.0 || height <= 0.0 {
165 return Rect {
166 x: 0.0,
167 y: 0.0,
168 width: 0.0,
169 height: 0.0,
170 };
171 }
172 Rect {
173 x: bounds.x + left,
174 y: bounds.y + top,
175 width,
176 height,
177 }
178}