1use alloc::vec;
42use alloc::vec::Vec;
43use core::sync::atomic::Ordering;
44
45use super::cmd::{pos_mix, Cmd, ICON_PAINTED};
46use super::Ui;
47
48const CS_SHIFT: u32 = 5;
50const SEED: u64 = 0x243F_6A88_85A3_08D3;
51const HK: u64 = 0x9E37_79B9_7F4A_7C15;
52const OTHER: u64 = 0x5851_F42D_4C95_7F2D;
54const AREA_MIN: i64 = 96 * 96;
56const GRIDS_MAX: usize = 16;
58const RECENT: u32 = 60;
60const EXPLORE: u32 = 16;
62const STATS_MAX: usize = 16;
64const IDLE_MAX: u32 = 4;
67const SKIP: u32 = 60;
68const TEXT_MAX_PX: i32 = 128;
70const GAP_MAX: usize = 2;
72
73#[derive(Clone, Copy)]
75struct Rec {
76 id: u32,
77 layer: u8,
78 i0: u32,
79 i1: u32,
80 ox: i64,
81 oy: i64,
82 clip: [i32; 4],
84}
85
86#[derive(Clone, Copy, Default)]
90struct Stat {
91 id: u32,
92 draw: u32,
93 copy: u32,
94 n: u32,
95 idle: u32,
96 skip: u32,
97}
98
99fn stat(v: &mut Vec<Stat>, id: u32) -> &mut Stat {
101 let k = match v.iter().position(|e| e.id == id) {
102 Some(k) => k,
103 None => {
104 if v.len() >= STATS_MAX {
105 v.remove(0);
106 }
107 v.push(Stat { id, ..Stat::default() });
108 v.len() - 1
109 }
110 };
111 &mut v[k]
112}
113
114#[derive(Default)]
116pub(crate) struct Grid {
117 id: u32,
118 r: [i32; 4],
120 ox: i64,
121 oy: i64,
122 kx: i64,
124 ky: i64,
125 nx: usize,
126 ny: usize,
127 h: Vec<u64>,
128 bad: Vec<bool>,
129 det: Vec<bool>,
131 layer: usize,
133 i0: usize,
134 i1: usize,
135 live: bool,
136 cs: Vec<[i32; 4]>,
138}
139
140#[derive(Default)]
145pub(crate) struct W {
146 recs: Vec<Rec>,
147 open: Vec<usize>,
148 seen: Vec<(u32, i64, i64, [i32; 4], u32)>,
149 tick: u32,
150 prev: Vec<Grid>,
151 shown: Vec<[i32; 4]>,
152 t0: u64,
153 mode: u8,
154 key: u32,
155 stats: Vec<Stat>,
156 tried: Vec<u32>,
158 copied: Vec<u32>,
159}
160
161impl W {
162 pub(crate) fn reset(&mut self) {
164 self.recs.clear();
165 self.open.clear();
166 }
167}
168
169#[derive(Default)]
171pub(crate) struct S {
172 pub(crate) frames: u32,
173 pub(crate) cells: u64,
174}
175
176enum Odd {
178 No,
179 All,
180 Corners(i32, [i32; 4]),
182}
183
184enum Flat {
187 No,
188 Fill([i32; 4], bool),
189 Hole([i32; 4]),
190}
191
192fn isect(a: [i32; 4], b: [i32; 4]) -> [i32; 4] {
193 [a[0].max(b[0]), a[1].max(b[1]), a[2].min(b[2]), a[3].min(b[3])]
194}
195
196fn empty(a: &[i32; 4]) -> bool {
197 a[2] <= a[0] || a[3] <= a[1]
198}
199
200#[inline]
201fn mix(h: u64, v: u64) -> u64 {
202 (h.rotate_left(7) ^ v).wrapping_mul(HK)
203}
204
205fn odd_moved(c: &Cmd, icons_exact: bool) -> Odd {
209 match *c {
210 Cmd::Rect { .. } | Cmd::Image { .. } | Cmd::Clip { .. } | Cmd::Unclip => Odd::No,
211 Cmd::Text { x, size, .. } => {
212 if x < 0 || size >> 8 > TEXT_MAX_PX { Odd::All } else { Odd::No }
213 }
214 Cmd::Icon { x, size, .. } => {
215 if !icons_exact || x < 0 || size >> 8 > TEXT_MAX_PX { Odd::All } else { Odd::No }
216 }
217 Cmd::RRect { x, y, w, h, r, t, .. } => {
218 let r = (r as i32).min(w.min(h) >> 1);
220 if r <= 0 { Odd::No } else { Odd::Corners(r.max(t as i32) + 1, [x, y, x.wrapping_add(w), y.wrapping_add(h)]) }
221 }
222 Cmd::Shadow { x, y, w, h, r, depth: d, .. } => {
223 if d <= 0 {
224 return Odd::No;
225 }
226 let down = (d as u32 / 3) as i32;
228 Odd::Corners(r.max(0) + d + 2, [x - d, y - d + down, x + w + d, y + h + d + down])
229 }
230 Cmd::PanelFx { .. } | Cmd::Circle { .. } | Cmd::Line { .. } | Cmd::Custom { .. } => Odd::All,
231 }
232}
233
234fn flat(c: &Cmd) -> Flat {
236 match *c {
237 Cmd::Rect { x, y, w, h, c } => Flat::Fill([x, y, x.wrapping_add(w), y.wrapping_add(h)], c >> 24 == 255),
238 Cmd::RRect { x, y, w, h, r, t, fill, .. } => {
239 let (r, t) = ((r as i32).min(w.min(h) >> 1), t as i32);
241 let k = if r <= 0 { t } else { r.max(t) + 1 };
242 let b = [x + k, y + k, x + w - k, y + h - k];
243 match fill >> 24 {
244 0 => Flat::Hole(b),
245 a => Flat::Fill(b, a == 255),
246 }
247 }
248 Cmd::Shadow { x, y, w, h, r, depth: d, .. } => {
249 if d <= 0 {
250 return Flat::Hole([x, y, x.wrapping_add(w), y.wrapping_add(h)]);
251 }
252 let down = (d as u32 / 3) as i32;
254 let r = r.max(0);
255 Flat::Fill([x + r + 1, y + down + r + 1, x + w - r - 1, y + down + h - r - 1], false)
256 }
257 _ => Flat::No,
258 }
259}
260
261fn detail(c: &Cmd) -> bool {
263 !matches!(c, Cmd::Rect { .. } | Cmd::RRect { .. } | Cmd::Shadow { .. } | Cmd::Clip { .. } | Cmd::Unclip)
264}
265
266impl Grid {
267 fn cells(&self, q: &[i32; 4]) -> Option<(usize, usize, usize, usize)> {
269 if empty(q) {
270 return None;
271 }
272 let a = ((q[0] as i64 - self.ox) >> CS_SHIFT) - self.kx;
273 let b = ((q[2] as i64 - 1 - self.ox) >> CS_SHIFT) - self.kx + 1;
274 let c = ((q[1] as i64 - self.oy) >> CS_SHIFT) - self.ky;
275 let d = ((q[3] as i64 - 1 - self.oy) >> CS_SHIFT) - self.ky + 1;
276 let (a, b) = (a.max(0), b.min(self.nx as i64));
277 let (c, d) = (c.max(0), d.min(self.ny as i64));
278 if a >= b || c >= d {
279 return None;
280 }
281 Some((a as usize, b as usize, c as usize, d as usize))
282 }
283
284 fn inner(&self, f: &[i32; 4], bound: &[i32; 4]) -> (usize, usize, usize, usize) {
287 let axis = |o: i64, k: i64, n: usize, f0: i32, f1: i32, b0: i32, b1: i32| {
288 let a = if b0 >= f0 { 0 } else { -((o - f0 as i64) >> CS_SHIFT) - k };
290 let e = if b1 <= f1 { n as i64 } else { ((f1 as i64 - o) >> CS_SHIFT) - k };
291 (a.clamp(0, n as i64) as usize, e.clamp(0, n as i64) as usize)
292 };
293 let (a, b) = axis(self.ox, self.kx, self.nx, f[0], f[2], bound[0], bound[2]);
294 let (c, d) = axis(self.oy, self.ky, self.ny, f[1], f[3], bound[1], bound[3]);
295 (a, b.max(a), c, d.max(c))
296 }
297
298 fn spoil(&mut self, q: &[i32; 4]) {
300 if let Some((a, b, c, d)) = self.cells(q) {
301 for j in c..d {
302 self.bad[j * self.nx + a..j * self.nx + b].fill(true);
303 }
304 }
305 }
306
307 fn content(&mut self, c: &Cmd, parts: (u64, i32, i32, u32), bb: [i32; 4], icons_exact: bool) {
309 let e = match self.cs.last() {
310 Some(k) => isect(bb, *k),
311 None => bb,
312 };
313 let Some((a, b, cc, d)) = self.cells(&e) else { return };
314 let (body, x, y, sh) = parts;
315 let mut hc = pos_mix(body, x as i64 - (self.ox << sh), y as i64 - (self.oy << sh));
316 hc = mix(hc, ((e[0] as i64 - self.ox) as u64) ^ ((e[1] as i64 - self.oy) as u64) << 32);
317 hc = mix(hc, ((e[2] as i64 - self.ox) as u64) ^ ((e[3] as i64 - self.oy) as u64) << 32);
318 let det = detail(c);
319 for j in cc..d {
320 let row = j * self.nx;
321 for v in &mut self.h[row + a..row + b] {
322 *v = mix(*v, hc);
323 }
324 if det {
325 self.det[row + a..row + b].fill(true);
326 }
327 }
328 match odd_moved(c, icons_exact) {
329 Odd::No => {}
330 Odd::All => self.spoil(&e),
331 Odd::Corners(k, q) => {
332 for s in [
333 [q[0], q[1], q[0] + k, q[1] + k],
334 [q[2] - k, q[1], q[2], q[1] + k],
335 [q[0], q[3] - k, q[0] + k, q[3]],
336 [q[2] - k, q[3] - k, q[2], q[3]],
337 ] {
338 self.spoil(&isect(s, e));
339 }
340 }
341 }
342 }
343
344 fn other(&mut self, c: &Cmd, bb: [i32; 4], clip: [i32; 4], exact: bool, hw: u64) {
346 let e = isect(bb, clip);
347 let ea = isect(e, self.r);
348 let Some((a, b, cc, d)) = self.cells(&ea) else { return };
349 let hw = hw ^ OTHER;
350 let fl = if exact { flat(c) } else { Flat::No };
351 let (ia, ib, ic, id) = match fl {
353 Flat::Fill(f, _) => self.inner(&isect(f, clip), &self.r),
354 Flat::Hole(f) => self.inner(&f, &self.r),
355 Flat::No => (0, 0, 0, 0),
356 };
357 let opaque = matches!(fl, Flat::Fill(_, true));
358 let fill = matches!(fl, Flat::Fill(..));
359 for j in cc..d {
360 let row = j * self.nx;
361 let inj = j >= ic && j < id;
362 for i in a..b {
363 let k = row + i;
364 if !(inj && i >= ia && i < ib) {
365 self.bad[k] = true;
366 } else if fill {
367 if opaque {
368 self.h[k] = SEED;
369 self.bad[k] = false;
370 self.det[k] = false;
371 }
372 self.h[k] = mix(self.h[k], hw);
373 }
374 }
375 }
376 }
377}
378
379#[derive(Default)]
381pub(crate) struct Pass {
382 grids: Vec<Grid>,
383 starts: Vec<usize>,
385 next: usize,
386 icons_exact: bool,
387 w: i32,
388 h: i32,
389}
390
391impl Pass {
392 pub(crate) fn on(&self) -> bool {
394 !self.grids.is_empty()
395 }
396
397 pub(crate) fn reach(&mut self, l: usize, i: usize, clip: [i32; 4], exact: bool) {
400 while let Some(&g) = self.starts.get(self.next) {
401 let gr = &mut self.grids[g];
402 if (gr.layer, gr.i0) > (l, i) {
403 break;
404 }
405 self.next += 1;
406 if (gr.layer, gr.i0) < (l, i) {
407 continue; }
409 let r = isect(clip, [0, 0, self.w, self.h]);
410 if !exact || empty(&r) || ((r[2] - r[0]) as i64 * (r[3] - r[1]) as i64) < AREA_MIN {
411 continue;
412 }
413 gr.r = r;
414 gr.kx = (r[0] as i64 - gr.ox) >> CS_SHIFT;
415 gr.ky = (r[1] as i64 - gr.oy) >> CS_SHIFT;
416 gr.nx = (((r[2] as i64 - 1 - gr.ox) >> CS_SHIFT) - gr.kx + 1) as usize;
417 gr.ny = (((r[3] as i64 - 1 - gr.oy) >> CS_SHIFT) - gr.ky + 1) as usize;
418 gr.h = vec![SEED; gr.nx * gr.ny];
419 gr.bad = vec![false; gr.nx * gr.ny];
420 gr.det = vec![false; gr.nx * gr.ny];
421 gr.live = true;
422 }
423 }
424
425 pub(crate) fn clip(&mut self, l: usize, i: usize, q: [i32; 4]) {
427 for g in self.grids.iter_mut().filter(|g| g.live && g.layer == l && i >= g.i0 && i < g.i1) {
428 let k = g.cs.last().map_or(q, |k| isect(*k, q));
429 g.cs.push(k);
430 }
431 }
432
433 pub(crate) fn unclip(&mut self, l: usize, i: usize) {
435 for g in self.grids.iter_mut().filter(|g| g.live && g.layer == l && i >= g.i0 && i < g.i1) {
436 g.cs.pop();
437 }
438 }
439
440 #[allow(clippy::too_many_arguments)]
443 pub(crate) fn cmd(&mut self, l: usize, i: usize, c: &Cmd, parts: (u64, i32, i32, u32), bb: [i32; 4], clip: [i32; 4], exact: bool, hw: u64) {
444 let icons_exact = self.icons_exact;
445 for g in self.grids.iter_mut().filter(|g| g.live) {
446 if g.layer == l && i >= g.i0 && i < g.i1 {
447 g.content(c, parts, bb, icons_exact);
448 } else {
449 g.other(c, bb, clip, exact, hw);
450 }
451 }
452 }
453}
454
455pub(crate) struct Plan {
458 pub(crate) covered: Vec<u8>,
459 moves: Vec<(u32, i32, i32)>,
460}
461
462impl Plan {
463 pub(crate) fn settle(&mut self, cols: usize, changed: impl Fn(usize) -> bool) -> usize {
467 let cv = &mut self.covered;
468 for (i, t) in cv.iter_mut().enumerate() {
469 if !changed(i) {
470 *t = 0; }
472 }
473 let mut left = 0;
474 for start in (0..cv.len()).step_by(cols.max(1)) {
475 let drawn = |cv: &[u8], i: usize| cv[start + i] == 0 && changed(start + i);
476 let mut i = 0;
477 while i < cols {
478 if cv[start + i] == 0 {
479 i += 1;
480 continue;
481 }
482 let mut e = i + 1;
484 while e < cols && cv[start + e] != 0 {
485 e += 1;
486 }
487 if e - i <= GAP_MAX && i > 0 && e < cols && drawn(cv, i - 1) && drawn(cv, e) {
488 cv[start + i..start + e].fill(0);
489 } else {
490 left += e - i;
491 }
492 i = e;
493 }
494 }
495 left
496 }
497}
498
499impl Ui {
500 pub fn scroll_copy_begin(&mut self, id: u32, ox: i64, oy: i64) {
506 let layer = self.wp.cmd.layer;
507 let i0 = self.wp.cmd.layers[layer].len() as u32;
508 let clip = self.wp.ctx.uiclip;
509 let s = &mut self.wp.scopy;
510 s.recs.push(Rec { id, layer: layer as u8, i0, i1: u32::MAX, ox, oy, clip });
511 s.open.push(s.recs.len() - 1);
512 }
513
514 pub fn scroll_copy_end(&mut self) {
516 let Ui { wp, .. } = self;
517 if let Some(k) = wp.scopy.open.pop() {
518 if let Some(r) = wp.scopy.recs.get_mut(k) {
519 r.i1 = wp.cmd.layers[r.layer as usize].len() as u32;
520 }
521 }
522 }
523
524 pub(crate) fn sc_pass(&mut self, w: i32, h: i32) -> Pass {
527 let icons_exact = self.icon_hook.is_none() && !ICON_PAINTED.load(Ordering::Relaxed);
528 let (ins, verify) = (self.m.ins.on, self.verify_dirty || self.m.touch.on);
530 let s = &mut self.wp.scopy;
531 s.shown.clear();
532 s.tick = s.tick.wrapping_add(1);
533 s.t0 = plat::time_us();
534 s.mode = 0;
535 for id in core::mem::take(&mut s.tried) {
538 let copied = s.copied.contains(&id);
539 let st = stat(&mut s.stats, id);
540 st.idle = if copied { 0 } else { st.idle + 1 };
541 if st.idle >= IDLE_MAX {
542 st.idle = 0;
543 st.skip = SKIP;
544 }
545 }
546 s.copied.clear();
547 let mut p = Pass { w, h, icons_exact, ..Pass::default() };
548 let mut seen = Vec::with_capacity(s.recs.len());
551 for r in s.recs.iter().filter(|r| r.i1 != u32::MAX && r.i1 > r.i0) {
552 let moved = match s.seen.iter().find(|e| e.0 == r.id) {
553 Some(e) if (e.1 == r.ox && e.2 == r.oy) || e.3 != r.clip => e.4,
554 Some(_) => s.tick,
555 None => s.tick.wrapping_sub(RECENT + 1),
556 };
557 seen.push((r.id, r.ox, r.oy, r.clip, moved));
558 let skip = match s.stats.iter_mut().find(|e| e.id == r.id) {
559 Some(st) if st.skip > 0 && !verify => {
560 st.skip -= 1;
561 true
562 }
563 _ => false,
564 };
565 if !ins && !skip && s.tick.wrapping_sub(moved) <= RECENT && p.grids.len() < GRIDS_MAX {
567 p.grids.push(Grid { id: r.id, ox: r.ox, oy: r.oy, layer: r.layer as usize, i0: r.i0 as usize, i1: r.i1 as usize, ..Grid::default() });
568 }
569 }
570 s.seen = seen;
571 let mut st: Vec<usize> = (0..p.grids.len()).collect();
572 st.sort_by_key(|&g| (p.grids[g].layer, p.grids[g].i0));
573 p.starts = st;
574 p
575 }
576
577 pub(crate) fn sc_plan(&mut self, p: Pass, cols: i32, rows: i32, allowed: bool) -> Option<Plan> {
581 let grids: Vec<Grid> = p
582 .grids
583 .into_iter()
584 .filter(|g| g.live)
585 .map(|mut g| {
586 g.cs = Vec::new();
587 g
588 })
589 .collect();
590 let prev = core::mem::replace(&mut self.wp.scopy.prev, grids);
591 if !allowed || prev.is_empty() {
592 return None;
593 }
594 let (w, h) = (p.w, p.h);
595 let rtl = self.rtl;
596 let flat_too = self.m.touch.on;
598 let grids = &self.wp.scopy.prev;
599 let mut cand: Vec<(&Grid, &Grid, i32, i32, [i32; 4])> = Vec::new();
601 let mut tried = Vec::new();
602 for g in grids {
603 let Some(o) = prev.iter().find(|o| o.id == g.id) else { continue };
604 let (dx, dy) = (g.ox - o.ox, g.oy - o.oy);
605 let r = g.r;
606 if o.r != r || (dx == 0 && dy == 0) || dx.abs() >= (r[2] - r[0]) as i64 || dy.abs() >= (r[3] - r[1]) as i64 || (rtl && dx != 0) {
607 continue;
608 }
609 let (dx, dy) = (dx as i32, dy as i32);
610 let q = [r[0].max(r[0] + dx), r[1].max(r[1] + dy), r[2].min(r[2] + dx), r[3].min(r[3] + dy)];
611 cand.push((g, o, dx, dy, q));
612 tried.push(g.id); }
614 self.wp.scopy.tried = tried;
615 let keep: Vec<bool> = (0..cand.len()).map(|a| (0..cand.len()).all(|b| a == b || empty(&isect(cand[a].0.r, cand[b].0.r)))).collect();
617 let mut covered = vec![0u8; cols as usize * rows as usize];
618 let mut moves = Vec::new();
619 for (m, &(g, o, dx, dy, q)) in cand.iter().enumerate() {
620 if !keep[m] {
621 continue;
622 }
623 let (sx, sy) = (g.kx - o.kx, g.ky - o.ky);
625 let mut v = vec![false; g.h.len()];
626 for j in 0..g.ny {
627 let pj = j as i64 + sy;
628 if pj < 0 || pj >= o.ny as i64 {
629 continue;
630 }
631 for i in 0..g.nx {
632 let pi = i as i64 + sx;
633 if pi < 0 || pi >= o.nx as i64 {
634 continue;
635 }
636 let (k, pk) = (j * g.nx + i, pj as usize * o.nx + pi as usize);
637 v[k] = !g.bad[k] && !o.bad[pk] && o.h[pk] == g.h[k];
638 }
639 }
640 let tag = moves.len() as u8 + 1;
643 let span = |c: i32, lim: i32, o: i64, k: i64| {
644 let (a, b) = (c << 5, ((c + 1) << 5).min(lim));
645 ((((a as i64 - o) >> CS_SHIFT) - k), (((b as i64 - 1 - o) >> CS_SHIFT) - k), a, b)
646 };
647 let (c0, c1) = ((q[0] >> 5).max(0), ((q[2] + 31) >> 5).min(cols));
648 let (r0, r1) = ((q[1] >> 5).max(0), ((q[3] + 31) >> 5).min(rows));
649 let xs: Vec<(i64, i64, i32, i32)> = (c0..c1).map(|c| span(c, w, g.ox, g.kx)).collect();
650 let mut any = false;
651 for row in r0..r1 {
652 let (j0, j1, y0, y1) = span(row, h, g.oy, g.ky);
653 if y0 < q[1] || y1 > q[3] || j0 < 0 || j1 >= g.ny as i64 {
654 continue;
655 }
656 for (n, &(i0, i1, x0, x1)) in xs.iter().enumerate() {
657 if x0 < q[0] || x1 > q[2] || i0 < 0 || i1 >= g.nx as i64 {
658 continue;
659 }
660 let ks = [j0 as usize * g.nx + i0 as usize, j0 as usize * g.nx + i1 as usize, j1 as usize * g.nx + i0 as usize, j1 as usize * g.nx + i1 as usize];
661 if ks.iter().all(|&k| v[k]) && (flat_too || ks.iter().any(|&k| g.det[k])) {
662 covered[(row * cols + c0 + n as i32) as usize] = tag;
663 any = true;
664 }
665 }
666 }
667 if any {
668 moves.push((g.id, dx, dy));
669 }
670 }
671 if moves.is_empty() {
672 return None;
673 }
674 Some(Plan { covered, moves })
675 }
676
677 pub(crate) fn sc_want(&mut self, plan: &Plan) -> bool {
681 let verify = self.verify_dirty || self.m.touch.on;
682 let s = &mut self.wp.scopy;
683 s.mode = 1;
684 s.key = plan.moves.first().map_or(0, |m| m.0);
685 if verify {
686 return true;
687 }
688 let st = stat(&mut s.stats, s.key);
689 st.n = st.n.wrapping_add(1);
690 if st.draw == 0 || st.copy == 0 {
691 return st.draw != 0; }
693 let better = st.copy < st.draw;
694 if st.n % EXPLORE == 0 { !better } else { better }
695 }
696
697 pub(crate) fn scopy_done(&mut self) {
700 let verify = self.verify_dirty;
701 let s = &mut self.wp.scopy;
702 let mode = core::mem::take(&mut s.mode);
703 if mode == 0 || verify {
704 return;
705 }
706 let dt = plat::time_us().saturating_sub(s.t0).clamp(1, 1 << 28) as u32;
707 let key = s.key;
708 if let Some(st) = s.stats.iter_mut().find(|e| e.id == key) {
709 let v = if mode == 2 { &mut st.copy } else { &mut st.draw };
710 *v = if *v == 0 { dt } else { (*v * 3 + dt) / 4 };
711 }
712 }
713
714 pub(crate) fn sc_copy(&mut self, plan: &Plan, cols: i32, rows: i32) {
718 let (w, h) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
719 let stride = self.wp.rt.fb.stride as usize;
720 let debug = self.ui_debug;
721 let mut done = 0u64;
722 for (m, &(id, dx, dy)) in plan.moves.iter().enumerate() {
723 let tag = m as u8 + 1;
724 let mut runs: Vec<(i32, i32, i32)> = Vec::new();
726 for row in 0..rows {
727 let cv = &plan.covered[(row * cols) as usize..((row + 1) * cols) as usize];
728 let mut col = 0;
729 while col < cols as usize {
730 if cv[col] != tag {
731 col += 1;
732 continue;
733 }
734 let mut end = col + 1;
735 while end < cols as usize && cv[end] == tag {
736 end += 1;
737 }
738 runs.push((row, col as i32, end as i32));
739 col = end;
740 }
741 }
742 if runs.is_empty() {
743 continue;
744 }
745 if dy > 0 {
748 runs.reverse();
749 }
750 let mut bbox = [i32::MAX, i32::MAX, i32::MIN, i32::MIN];
751 let ncell: u64 = runs.iter().map(|r| (r.2 - r.1) as u64).sum();
752 let px = &mut self.wp.rt.fb.px;
753 let mut k = 0;
754 while k < runs.len() {
755 let row = runs[k].0;
756 let mut e = k;
757 while e < runs.len() && runs[e].0 == row {
758 e += 1;
759 }
760 let mut rr: Vec<(i32, i32)> = runs[k..e].iter().map(|r| (r.1, r.2)).collect();
761 rr.sort_unstable();
762 if dx > 0 {
763 rr.reverse();
764 }
765 let (y0, y1) = (row << 5, ((row + 1) << 5).min(h));
766 for t in 0..(y1 - y0) {
767 let y = if dy > 0 { y1 - 1 - t } else { y0 + t };
768 let sy = y - dy;
769 for &(c0, c1) in &rr {
770 let (x0, x1) = (c0 << 5, (c1 << 5).min(w));
771 let src = sy as usize * stride + (x0 - dx) as usize * 4;
772 let dst = y as usize * stride + x0 as usize * 4;
773 px.copy_within(src..src + (x1 - x0) as usize * 4, dst);
774 }
775 }
776 for &(c0, c1) in &rr {
777 bbox = [bbox[0].min(c0 << 5), bbox[1].min(y0), bbox[2].max((c1 << 5).min(w)), bbox[3].max(y1)];
778 }
779 k = e;
780 }
781 self.wp.scopy.shown.push([bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]]);
782 self.wp.scopy.copied.push(id);
783 done += ncell;
784 if debug {
785 plat::write_stderr(alloc::format!("render: scroll copy #{:x} by {},{}: {} cells\n", id, dx, dy, ncell).as_bytes());
786 }
787 }
788 if done != 0 {
789 self.wp.scopy.mode = 2;
790 self.m.scopy.frames = self.m.scopy.frames.wrapping_add(1);
791 self.m.scopy.cells = self.m.scopy.cells.wrapping_add(done);
792 }
793 }
794
795 pub(crate) fn scopy_present(&mut self, n: i32) -> i32 {
798 let s = &self.wp.scopy.shown;
799 if n < 0 || s.is_empty() {
800 return n;
801 }
802 self.drects.truncate(n as usize);
803 self.drects.extend_from_slice(s);
804 n + s.len() as i32
805 }
806}