use alloc::vec;
use alloc::vec::Vec;
use core::sync::atomic::Ordering;
use super::cmd::{pos_mix, Cmd, ICON_PAINTED};
use super::Ui;
const CS_SHIFT: u32 = 5;
const SEED: u64 = 0x243F_6A88_85A3_08D3;
const HK: u64 = 0x9E37_79B9_7F4A_7C15;
const OTHER: u64 = 0x5851_F42D_4C95_7F2D;
const AREA_MIN: i64 = 96 * 96;
const GRIDS_MAX: usize = 16;
const RECENT: u32 = 60;
const EXPLORE: u32 = 16;
const STATS_MAX: usize = 16;
const IDLE_MAX: u32 = 4;
const SKIP: u32 = 60;
const TEXT_MAX_PX: i32 = 128;
const GAP_MAX: usize = 2;
#[derive(Clone, Copy)]
struct Rec {
id: u32,
layer: u8,
i0: u32,
i1: u32,
ox: i64,
oy: i64,
clip: [i32; 4],
}
#[derive(Clone, Copy, Default)]
struct Stat {
id: u32,
draw: u32,
copy: u32,
n: u32,
idle: u32,
skip: u32,
}
fn stat(v: &mut Vec<Stat>, id: u32) -> &mut Stat {
let k = match v.iter().position(|e| e.id == id) {
Some(k) => k,
None => {
if v.len() >= STATS_MAX {
v.remove(0);
}
v.push(Stat { id, ..Stat::default() });
v.len() - 1
}
};
&mut v[k]
}
#[derive(Default)]
pub(crate) struct Grid {
id: u32,
r: [i32; 4],
ox: i64,
oy: i64,
kx: i64,
ky: i64,
nx: usize,
ny: usize,
h: Vec<u64>,
bad: Vec<bool>,
det: Vec<bool>,
layer: usize,
i0: usize,
i1: usize,
live: bool,
cs: Vec<[i32; 4]>,
}
#[derive(Default)]
pub(crate) struct W {
recs: Vec<Rec>,
open: Vec<usize>,
seen: Vec<(u32, i64, i64, [i32; 4], u32)>,
tick: u32,
prev: Vec<Grid>,
shown: Vec<[i32; 4]>,
t0: u64,
mode: u8,
key: u32,
stats: Vec<Stat>,
tried: Vec<u32>,
copied: Vec<u32>,
}
impl W {
pub(crate) fn reset(&mut self) {
self.recs.clear();
self.open.clear();
}
}
#[derive(Default)]
pub(crate) struct S {
pub(crate) frames: u32,
pub(crate) cells: u64,
}
enum Odd {
No,
All,
Corners(i32, [i32; 4]),
}
enum Flat {
No,
Fill([i32; 4], bool),
Hole([i32; 4]),
}
fn isect(a: [i32; 4], b: [i32; 4]) -> [i32; 4] {
[a[0].max(b[0]), a[1].max(b[1]), a[2].min(b[2]), a[3].min(b[3])]
}
fn empty(a: &[i32; 4]) -> bool {
a[2] <= a[0] || a[3] <= a[1]
}
#[inline]
fn mix(h: u64, v: u64) -> u64 {
(h.rotate_left(7) ^ v).wrapping_mul(HK)
}
fn odd_moved(c: &Cmd, icons_exact: bool) -> Odd {
match *c {
Cmd::Rect { .. } | Cmd::Image { .. } | Cmd::Clip { .. } | Cmd::Unclip => Odd::No,
Cmd::Text { x, size, .. } => {
if x < 0 || size >> 8 > TEXT_MAX_PX { Odd::All } else { Odd::No }
}
Cmd::Icon { x, size, .. } => {
if !icons_exact || x < 0 || size >> 8 > TEXT_MAX_PX { Odd::All } else { Odd::No }
}
Cmd::RRect { x, y, w, h, r, t, .. } => {
let r = (r as i32).min(w.min(h) >> 1);
if r <= 0 { Odd::No } else { Odd::Corners(r.max(t as i32) + 1, [x, y, x.wrapping_add(w), y.wrapping_add(h)]) }
}
Cmd::Shadow { x, y, w, h, r, depth: d, .. } => {
if d <= 0 {
return Odd::No;
}
let down = (d as u32 / 3) as i32;
Odd::Corners(r.max(0) + d + 2, [x - d, y - d + down, x + w + d, y + h + d + down])
}
Cmd::PanelFx { .. } | Cmd::Circle { .. } | Cmd::Line { .. } | Cmd::Custom { .. } => Odd::All,
}
}
fn flat(c: &Cmd) -> Flat {
match *c {
Cmd::Rect { x, y, w, h, c } => Flat::Fill([x, y, x.wrapping_add(w), y.wrapping_add(h)], c >> 24 == 255),
Cmd::RRect { x, y, w, h, r, t, fill, .. } => {
let (r, t) = ((r as i32).min(w.min(h) >> 1), t as i32);
let k = if r <= 0 { t } else { r.max(t) + 1 };
let b = [x + k, y + k, x + w - k, y + h - k];
match fill >> 24 {
0 => Flat::Hole(b),
a => Flat::Fill(b, a == 255),
}
}
Cmd::Shadow { x, y, w, h, r, depth: d, .. } => {
if d <= 0 {
return Flat::Hole([x, y, x.wrapping_add(w), y.wrapping_add(h)]);
}
let down = (d as u32 / 3) as i32;
let r = r.max(0);
Flat::Fill([x + r + 1, y + down + r + 1, x + w - r - 1, y + down + h - r - 1], false)
}
_ => Flat::No,
}
}
fn detail(c: &Cmd) -> bool {
!matches!(c, Cmd::Rect { .. } | Cmd::RRect { .. } | Cmd::Shadow { .. } | Cmd::Clip { .. } | Cmd::Unclip)
}
impl Grid {
fn cells(&self, q: &[i32; 4]) -> Option<(usize, usize, usize, usize)> {
if empty(q) {
return None;
}
let a = ((q[0] as i64 - self.ox) >> CS_SHIFT) - self.kx;
let b = ((q[2] as i64 - 1 - self.ox) >> CS_SHIFT) - self.kx + 1;
let c = ((q[1] as i64 - self.oy) >> CS_SHIFT) - self.ky;
let d = ((q[3] as i64 - 1 - self.oy) >> CS_SHIFT) - self.ky + 1;
let (a, b) = (a.max(0), b.min(self.nx as i64));
let (c, d) = (c.max(0), d.min(self.ny as i64));
if a >= b || c >= d {
return None;
}
Some((a as usize, b as usize, c as usize, d as usize))
}
fn inner(&self, f: &[i32; 4], bound: &[i32; 4]) -> (usize, usize, usize, usize) {
let axis = |o: i64, k: i64, n: usize, f0: i32, f1: i32, b0: i32, b1: i32| {
let a = if b0 >= f0 { 0 } else { -((o - f0 as i64) >> CS_SHIFT) - k };
let e = if b1 <= f1 { n as i64 } else { ((f1 as i64 - o) >> CS_SHIFT) - k };
(a.clamp(0, n as i64) as usize, e.clamp(0, n as i64) as usize)
};
let (a, b) = axis(self.ox, self.kx, self.nx, f[0], f[2], bound[0], bound[2]);
let (c, d) = axis(self.oy, self.ky, self.ny, f[1], f[3], bound[1], bound[3]);
(a, b.max(a), c, d.max(c))
}
fn spoil(&mut self, q: &[i32; 4]) {
if let Some((a, b, c, d)) = self.cells(q) {
for j in c..d {
self.bad[j * self.nx + a..j * self.nx + b].fill(true);
}
}
}
fn content(&mut self, c: &Cmd, parts: (u64, i32, i32, u32), bb: [i32; 4], icons_exact: bool) {
let e = match self.cs.last() {
Some(k) => isect(bb, *k),
None => bb,
};
let Some((a, b, cc, d)) = self.cells(&e) else { return };
let (body, x, y, sh) = parts;
let mut hc = pos_mix(body, x as i64 - (self.ox << sh), y as i64 - (self.oy << sh));
hc = mix(hc, ((e[0] as i64 - self.ox) as u64) ^ ((e[1] as i64 - self.oy) as u64) << 32);
hc = mix(hc, ((e[2] as i64 - self.ox) as u64) ^ ((e[3] as i64 - self.oy) as u64) << 32);
let det = detail(c);
for j in cc..d {
let row = j * self.nx;
for v in &mut self.h[row + a..row + b] {
*v = mix(*v, hc);
}
if det {
self.det[row + a..row + b].fill(true);
}
}
match odd_moved(c, icons_exact) {
Odd::No => {}
Odd::All => self.spoil(&e),
Odd::Corners(k, q) => {
for s in [
[q[0], q[1], q[0] + k, q[1] + k],
[q[2] - k, q[1], q[2], q[1] + k],
[q[0], q[3] - k, q[0] + k, q[3]],
[q[2] - k, q[3] - k, q[2], q[3]],
] {
self.spoil(&isect(s, e));
}
}
}
}
fn other(&mut self, c: &Cmd, bb: [i32; 4], clip: [i32; 4], exact: bool, hw: u64) {
let e = isect(bb, clip);
let ea = isect(e, self.r);
let Some((a, b, cc, d)) = self.cells(&ea) else { return };
let hw = hw ^ OTHER;
let fl = if exact { flat(c) } else { Flat::No };
let (ia, ib, ic, id) = match fl {
Flat::Fill(f, _) => self.inner(&isect(f, clip), &self.r),
Flat::Hole(f) => self.inner(&f, &self.r),
Flat::No => (0, 0, 0, 0),
};
let opaque = matches!(fl, Flat::Fill(_, true));
let fill = matches!(fl, Flat::Fill(..));
for j in cc..d {
let row = j * self.nx;
let inj = j >= ic && j < id;
for i in a..b {
let k = row + i;
if !(inj && i >= ia && i < ib) {
self.bad[k] = true;
} else if fill {
if opaque {
self.h[k] = SEED;
self.bad[k] = false;
self.det[k] = false;
}
self.h[k] = mix(self.h[k], hw);
}
}
}
}
}
#[derive(Default)]
pub(crate) struct Pass {
grids: Vec<Grid>,
starts: Vec<usize>,
next: usize,
icons_exact: bool,
w: i32,
h: i32,
}
impl Pass {
pub(crate) fn on(&self) -> bool {
!self.grids.is_empty()
}
pub(crate) fn reach(&mut self, l: usize, i: usize, clip: [i32; 4], exact: bool) {
while let Some(&g) = self.starts.get(self.next) {
let gr = &mut self.grids[g];
if (gr.layer, gr.i0) > (l, i) {
break;
}
self.next += 1;
if (gr.layer, gr.i0) < (l, i) {
continue; }
let r = isect(clip, [0, 0, self.w, self.h]);
if !exact || empty(&r) || ((r[2] - r[0]) as i64 * (r[3] - r[1]) as i64) < AREA_MIN {
continue;
}
gr.r = r;
gr.kx = (r[0] as i64 - gr.ox) >> CS_SHIFT;
gr.ky = (r[1] as i64 - gr.oy) >> CS_SHIFT;
gr.nx = (((r[2] as i64 - 1 - gr.ox) >> CS_SHIFT) - gr.kx + 1) as usize;
gr.ny = (((r[3] as i64 - 1 - gr.oy) >> CS_SHIFT) - gr.ky + 1) as usize;
gr.h = vec![SEED; gr.nx * gr.ny];
gr.bad = vec![false; gr.nx * gr.ny];
gr.det = vec![false; gr.nx * gr.ny];
gr.live = true;
}
}
pub(crate) fn clip(&mut self, l: usize, i: usize, q: [i32; 4]) {
for g in self.grids.iter_mut().filter(|g| g.live && g.layer == l && i >= g.i0 && i < g.i1) {
let k = g.cs.last().map_or(q, |k| isect(*k, q));
g.cs.push(k);
}
}
pub(crate) fn unclip(&mut self, l: usize, i: usize) {
for g in self.grids.iter_mut().filter(|g| g.live && g.layer == l && i >= g.i0 && i < g.i1) {
g.cs.pop();
}
}
#[allow(clippy::too_many_arguments)]
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) {
let icons_exact = self.icons_exact;
for g in self.grids.iter_mut().filter(|g| g.live) {
if g.layer == l && i >= g.i0 && i < g.i1 {
g.content(c, parts, bb, icons_exact);
} else {
g.other(c, bb, clip, exact, hw);
}
}
}
}
pub(crate) struct Plan {
pub(crate) covered: Vec<u8>,
moves: Vec<(u32, i32, i32)>,
}
impl Plan {
pub(crate) fn settle(&mut self, cols: usize, changed: impl Fn(usize) -> bool) -> usize {
let cv = &mut self.covered;
for (i, t) in cv.iter_mut().enumerate() {
if !changed(i) {
*t = 0; }
}
let mut left = 0;
for start in (0..cv.len()).step_by(cols.max(1)) {
let drawn = |cv: &[u8], i: usize| cv[start + i] == 0 && changed(start + i);
let mut i = 0;
while i < cols {
if cv[start + i] == 0 {
i += 1;
continue;
}
let mut e = i + 1;
while e < cols && cv[start + e] != 0 {
e += 1;
}
if e - i <= GAP_MAX && i > 0 && e < cols && drawn(cv, i - 1) && drawn(cv, e) {
cv[start + i..start + e].fill(0);
} else {
left += e - i;
}
i = e;
}
}
left
}
}
impl Ui {
pub fn scroll_copy_begin(&mut self, id: u32, ox: i64, oy: i64) {
let layer = self.wp.cmd.layer;
let i0 = self.wp.cmd.layers[layer].len() as u32;
let clip = self.wp.ctx.uiclip;
let s = &mut self.wp.scopy;
s.recs.push(Rec { id, layer: layer as u8, i0, i1: u32::MAX, ox, oy, clip });
s.open.push(s.recs.len() - 1);
}
pub fn scroll_copy_end(&mut self) {
let Ui { wp, .. } = self;
if let Some(k) = wp.scopy.open.pop() {
if let Some(r) = wp.scopy.recs.get_mut(k) {
r.i1 = wp.cmd.layers[r.layer as usize].len() as u32;
}
}
}
pub(crate) fn sc_pass(&mut self, w: i32, h: i32) -> Pass {
let icons_exact = self.icon_hook.is_none() && !ICON_PAINTED.load(Ordering::Relaxed);
let (ins, verify) = (self.m.ins.on, self.verify_dirty || self.m.touch.on);
let s = &mut self.wp.scopy;
s.shown.clear();
s.tick = s.tick.wrapping_add(1);
s.t0 = plat::time_us();
s.mode = 0;
for id in core::mem::take(&mut s.tried) {
let copied = s.copied.contains(&id);
let st = stat(&mut s.stats, id);
st.idle = if copied { 0 } else { st.idle + 1 };
if st.idle >= IDLE_MAX {
st.idle = 0;
st.skip = SKIP;
}
}
s.copied.clear();
let mut p = Pass { w, h, icons_exact, ..Pass::default() };
let mut seen = Vec::with_capacity(s.recs.len());
for r in s.recs.iter().filter(|r| r.i1 != u32::MAX && r.i1 > r.i0) {
let moved = match s.seen.iter().find(|e| e.0 == r.id) {
Some(e) if (e.1 == r.ox && e.2 == r.oy) || e.3 != r.clip => e.4,
Some(_) => s.tick,
None => s.tick.wrapping_sub(RECENT + 1),
};
seen.push((r.id, r.ox, r.oy, r.clip, moved));
let skip = match s.stats.iter_mut().find(|e| e.id == r.id) {
Some(st) if st.skip > 0 && !verify => {
st.skip -= 1;
true
}
_ => false,
};
if !ins && !skip && s.tick.wrapping_sub(moved) <= RECENT && p.grids.len() < GRIDS_MAX {
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() });
}
}
s.seen = seen;
let mut st: Vec<usize> = (0..p.grids.len()).collect();
st.sort_by_key(|&g| (p.grids[g].layer, p.grids[g].i0));
p.starts = st;
p
}
pub(crate) fn sc_plan(&mut self, p: Pass, cols: i32, rows: i32, allowed: bool) -> Option<Plan> {
let grids: Vec<Grid> = p
.grids
.into_iter()
.filter(|g| g.live)
.map(|mut g| {
g.cs = Vec::new();
g
})
.collect();
let prev = core::mem::replace(&mut self.wp.scopy.prev, grids);
if !allowed || prev.is_empty() {
return None;
}
let (w, h) = (p.w, p.h);
let rtl = self.rtl;
let flat_too = self.m.touch.on;
let grids = &self.wp.scopy.prev;
let mut cand: Vec<(&Grid, &Grid, i32, i32, [i32; 4])> = Vec::new();
let mut tried = Vec::new();
for g in grids {
let Some(o) = prev.iter().find(|o| o.id == g.id) else { continue };
let (dx, dy) = (g.ox - o.ox, g.oy - o.oy);
let r = g.r;
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) {
continue;
}
let (dx, dy) = (dx as i32, dy as i32);
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)];
cand.push((g, o, dx, dy, q));
tried.push(g.id); }
self.wp.scopy.tried = tried;
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();
let mut covered = vec![0u8; cols as usize * rows as usize];
let mut moves = Vec::new();
for (m, &(g, o, dx, dy, q)) in cand.iter().enumerate() {
if !keep[m] {
continue;
}
let (sx, sy) = (g.kx - o.kx, g.ky - o.ky);
let mut v = vec![false; g.h.len()];
for j in 0..g.ny {
let pj = j as i64 + sy;
if pj < 0 || pj >= o.ny as i64 {
continue;
}
for i in 0..g.nx {
let pi = i as i64 + sx;
if pi < 0 || pi >= o.nx as i64 {
continue;
}
let (k, pk) = (j * g.nx + i, pj as usize * o.nx + pi as usize);
v[k] = !g.bad[k] && !o.bad[pk] && o.h[pk] == g.h[k];
}
}
let tag = moves.len() as u8 + 1;
let span = |c: i32, lim: i32, o: i64, k: i64| {
let (a, b) = (c << 5, ((c + 1) << 5).min(lim));
((((a as i64 - o) >> CS_SHIFT) - k), (((b as i64 - 1 - o) >> CS_SHIFT) - k), a, b)
};
let (c0, c1) = ((q[0] >> 5).max(0), ((q[2] + 31) >> 5).min(cols));
let (r0, r1) = ((q[1] >> 5).max(0), ((q[3] + 31) >> 5).min(rows));
let xs: Vec<(i64, i64, i32, i32)> = (c0..c1).map(|c| span(c, w, g.ox, g.kx)).collect();
let mut any = false;
for row in r0..r1 {
let (j0, j1, y0, y1) = span(row, h, g.oy, g.ky);
if y0 < q[1] || y1 > q[3] || j0 < 0 || j1 >= g.ny as i64 {
continue;
}
for (n, &(i0, i1, x0, x1)) in xs.iter().enumerate() {
if x0 < q[0] || x1 > q[2] || i0 < 0 || i1 >= g.nx as i64 {
continue;
}
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];
if ks.iter().all(|&k| v[k]) && (flat_too || ks.iter().any(|&k| g.det[k])) {
covered[(row * cols + c0 + n as i32) as usize] = tag;
any = true;
}
}
}
if any {
moves.push((g.id, dx, dy));
}
}
if moves.is_empty() {
return None;
}
Some(Plan { covered, moves })
}
pub(crate) fn sc_want(&mut self, plan: &Plan) -> bool {
let verify = self.verify_dirty || self.m.touch.on;
let s = &mut self.wp.scopy;
s.mode = 1;
s.key = plan.moves.first().map_or(0, |m| m.0);
if verify {
return true;
}
let st = stat(&mut s.stats, s.key);
st.n = st.n.wrapping_add(1);
if st.draw == 0 || st.copy == 0 {
return st.draw != 0; }
let better = st.copy < st.draw;
if st.n % EXPLORE == 0 { !better } else { better }
}
pub(crate) fn scopy_done(&mut self) {
let verify = self.verify_dirty;
let s = &mut self.wp.scopy;
let mode = core::mem::take(&mut s.mode);
if mode == 0 || verify {
return;
}
let dt = plat::time_us().saturating_sub(s.t0).clamp(1, 1 << 28) as u32;
let key = s.key;
if let Some(st) = s.stats.iter_mut().find(|e| e.id == key) {
let v = if mode == 2 { &mut st.copy } else { &mut st.draw };
*v = if *v == 0 { dt } else { (*v * 3 + dt) / 4 };
}
}
pub(crate) fn sc_copy(&mut self, plan: &Plan, cols: i32, rows: i32) {
let (w, h) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
let stride = self.wp.rt.fb.stride as usize;
let debug = self.ui_debug;
let mut done = 0u64;
for (m, &(id, dx, dy)) in plan.moves.iter().enumerate() {
let tag = m as u8 + 1;
let mut runs: Vec<(i32, i32, i32)> = Vec::new();
for row in 0..rows {
let cv = &plan.covered[(row * cols) as usize..((row + 1) * cols) as usize];
let mut col = 0;
while col < cols as usize {
if cv[col] != tag {
col += 1;
continue;
}
let mut end = col + 1;
while end < cols as usize && cv[end] == tag {
end += 1;
}
runs.push((row, col as i32, end as i32));
col = end;
}
}
if runs.is_empty() {
continue;
}
if dy > 0 {
runs.reverse();
}
let mut bbox = [i32::MAX, i32::MAX, i32::MIN, i32::MIN];
let ncell: u64 = runs.iter().map(|r| (r.2 - r.1) as u64).sum();
let px = &mut self.wp.rt.fb.px;
let mut k = 0;
while k < runs.len() {
let row = runs[k].0;
let mut e = k;
while e < runs.len() && runs[e].0 == row {
e += 1;
}
let mut rr: Vec<(i32, i32)> = runs[k..e].iter().map(|r| (r.1, r.2)).collect();
rr.sort_unstable();
if dx > 0 {
rr.reverse();
}
let (y0, y1) = (row << 5, ((row + 1) << 5).min(h));
for t in 0..(y1 - y0) {
let y = if dy > 0 { y1 - 1 - t } else { y0 + t };
let sy = y - dy;
for &(c0, c1) in &rr {
let (x0, x1) = (c0 << 5, (c1 << 5).min(w));
let src = sy as usize * stride + (x0 - dx) as usize * 4;
let dst = y as usize * stride + x0 as usize * 4;
px.copy_within(src..src + (x1 - x0) as usize * 4, dst);
}
}
for &(c0, c1) in &rr {
bbox = [bbox[0].min(c0 << 5), bbox[1].min(y0), bbox[2].max((c1 << 5).min(w)), bbox[3].max(y1)];
}
k = e;
}
self.wp.scopy.shown.push([bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]]);
self.wp.scopy.copied.push(id);
done += ncell;
if debug {
plat::write_stderr(alloc::format!("render: scroll copy #{:x} by {},{}: {} cells\n", id, dx, dy, ncell).as_bytes());
}
}
if done != 0 {
self.wp.scopy.mode = 2;
self.m.scopy.frames = self.m.scopy.frames.wrapping_add(1);
self.m.scopy.cells = self.m.scopy.cells.wrapping_add(done);
}
}
pub(crate) fn scopy_present(&mut self, n: i32) -> i32 {
let s = &self.wp.scopy.shown;
if n < 0 || s.is_empty() {
return n;
}
self.drects.truncate(n as usize);
self.drects.extend_from_slice(s);
n + s.len() as i32
}
}