diff --git a/crates/k-ui/src/glass.rs b/crates/k-ui/src/glass.rs index e292e8c..90e2733 100644 --- a/crates/k-ui/src/glass.rs +++ b/crates/k-ui/src/glass.rs @@ -9,6 +9,7 @@ use alloc::vec; use alloc::vec::Vec; use crate::layout::Rect; +use crate::pixels::{between_corners, blend, corner_row, cover, height, span}; use crate::theme::{Rgba, Theme}; /// The backdrop is made at 1 / `SCALE` of the window, each way. @@ -83,8 +84,10 @@ impl Backdrop { } } } - let mut glass = Image { w: sw, h: sh, px: wall.px.clone() }; - blur(&mut glass, (t.blur_radius as usize).div_ceil(SCALE)); + // Blurred at half this size again (it is soft), then brought back. + let mut small = halve(&wall); + blur(&mut small, (t.blur_radius as usize).div_ceil(2 * SCALE)); + let mut glass = double(&small, sw, sh); let opacity = t.panel_opacity as u32 * 256 / 100; for p in &mut glass.px { *p = mix(*p, t.surface, opacity); @@ -98,46 +101,35 @@ impl Backdrop { copy(&self.wall, pm, width, part, 0.0); } + /// `part` of `pm` moved back toward the wallpaper by `k` 256ths: what + /// is drawn there fading in. + pub fn fade(&self, pm: &mut [u8], width: usize, part: Rect, k: u32) { + let (cols, rows) = span(part, width, height(pm, width)); + for y in rows { + let src = &self.wall.px[(y / SCALE).min(self.wall.h - 1) * self.wall.w..][..self.wall.w]; + let row = pm[(y * width + cols.start) * 4..(y * width + cols.end) * 4].as_chunks_mut::<4>().0; + for (i, d) in row.iter_mut().enumerate() { + blend(d, src[((cols.start + i) / SCALE).min(self.wall.w - 1)], k); + } + } + } + /// A glass panel over `r`, its corners rounded by `radius`. pub fn glass(&self, pm: &mut [u8], width: usize, r: Rect, radius: f32) { copy(&self.glass, pm, width, r, radius); } } -/// The pixels of `r` (whole ones) inside `pm`: columns and rows. -fn span(r: Rect, width: usize, height: usize) -> (core::ops::Range, core::ops::Range) { - let (x0, y0) = (r.x.max(0.0) as usize, r.y.max(0.0) as usize); - let (x1, y1) = (((r.x + r.w).max(0.0) as usize).min(width), ((r.y + r.h).max(0.0) as usize).min(height)); - (x0..x1.max(x0), y0..y1.max(y0)) -} - -/// How much of pixel (`x`, `y`) is inside `r` with its corners rounded by -/// `radius`, in 256ths: worked out only in the corners. -fn cover(r: Rect, radius: f32, x: usize, y: usize) -> u32 { - let (fx, fy) = (x as f32 + 0.5, y as f32 + 0.5); - let dx = (r.x + radius - fx).max(fx - (r.x + r.w - radius)); - let dy = (r.y + radius - fy).max(fy - (r.y + r.h - radius)); - if dx <= 0.0 || dy <= 0.0 { - return 256; - } - ((radius + 0.5 - libm::sqrtf(dx * dx + dy * dy)).clamp(0.0, 1.0) * 256.0) as u32 -} - -fn blend(d: &mut [u8], s: [u8; 4], k: u32) { - for (d, s) in d.iter_mut().zip(s) { - *d = ((s as u32 * k + *d as u32 * (256 - k)) >> 8) as u8; - } -} - /// Copies `img` (scaled up) into `r` of `pm`, the corners rounded by /// `radius` and anti-aliased there. fn copy(img: &Image, pm: &mut [u8], width: usize, r: Rect, radius: f32) { let radius = radius.min(r.w / 2.0).min(r.h / 2.0).max(0.0); - let (cols, rows) = span(r, width, pm.len() / 4 / width.max(1)); + let (cols, rows) = span(r, width, height(pm, width)); + let mid = between_corners(r, radius, &cols); let (x0, x1) = (cols.start, cols.end); let mut copied: Option = None; for y in rows { - let corner = radius > 0.0 && ((y as f32) < r.y + radius || (y as f32 + 1.0) > r.y + r.h - radius); + let corner = corner_row(r, radius, y); let at = y * width * 4; // A row made from the same source row as the one above: the same. if let Some(above) = copied.filter(|a| *a + 1 == y && a / SCALE == y / SCALE && !corner) { @@ -147,118 +139,150 @@ fn copy(img: &Image, pm: &mut [u8], width: usize, r: Rect, radius: f32) { continue; } let src = &img.px[(y / SCALE).min(img.h - 1) * img.w..][..img.w]; - let row = &mut pm[at + x0 * 4..at + x1 * 4]; + let straight = if corner { mid.clone() } else { cols.clone() }; + let row = &mut pm[at + straight.start * 4..at + straight.end * 4]; + for (i, d) in row.as_chunks_mut::<4>().0.iter_mut().enumerate() { + *d = src[((straight.start + i) / SCALE).min(img.w - 1)]; + } if corner { - for (i, d) in row.as_chunks_mut::<4>().0.iter_mut().enumerate() { - let s = src[((x0 + i) / SCALE).min(img.w - 1)]; - match cover(r, radius, x0 + i, y) { - 256 => d.copy_from_slice(&s), + let row = pm[at..at + width * 4].as_chunks_mut::<4>().0; + for x in (x0..mid.start).chain(mid.end..x1) { + let s = src[(x / SCALE).min(img.w - 1)]; + match cover(r, radius, x, y) { + 256 => row[x] = s, 0 => {} - k => blend(d, s, k), + k => blend(&mut row[x], s, k), } } - copied = None; - } else { - for (i, d) in row.as_chunks_mut::<4>().0.iter_mut().enumerate() { - d.copy_from_slice(&src[((x0 + i) / SCALE).min(img.w - 1)]); - } - copied = Some(y); } + copied = (!corner).then_some(y); } } -/// The wet highlight: white along the top of `r`, `alpha` (percent) at -/// the edge and gone `depth` pixels down. -pub fn gloss(pm: &mut [u8], width: usize, r: Rect, radius: f32, alpha: u8, depth: f32) { - let radius = radius.min(r.w / 2.0).min(r.h / 2.0).max(0.0); - let top = Rect { h: depth.min(r.h), ..r }; - let (cols, rows) = span(top, width, pm.len() / 4 / width.max(1)); - let white = [255u8; 4]; - for y in rows { - let fade = 1.0 - (y as f32 + 0.5 - r.y) / depth; - let a = (fade.max(0.0) * alpha as f32 * 256.0 / 100.0) as u32; - let row = &mut pm[y * width * 4..(y + 1) * width * 4]; - for x in cols.clone() { - let k = (cover(r, radius, x, y) * a) >> 8; - if k > 0 { - blend(&mut row[x * 4..x * 4 + 4], white, k); +/// `img` at half the size each way, each pixel the average of four. +fn halve(img: &Image) -> Image { + let (w, h) = (img.w.div_ceil(2), img.h.div_ceil(2)); + let mut px = vec![[0u8; 4]; w * h]; + for y in 0..h { + let (r0, r1) = (2 * y, (2 * y + 1).min(img.h - 1)); + for x in 0..w { + let (c0, c1) = (2 * x, (2 * x + 1).min(img.w - 1)); + let mut sum = [0u32; 4]; + for p in [img.px[r0 * img.w + c0], img.px[r0 * img.w + c1], img.px[r1 * img.w + c0], img.px[r1 * img.w + c1]] { + add(&mut sum, p); } + px[y * w + x] = sum.map(|s| (s / 4) as u8); } } + Image { w, h, px } } -/// How far (`x`, `y`) is outside `r` with its corners rounded by `radius` -/// (negative inside). -fn outside(r: Rect, radius: f32, x: f32, y: f32) -> f32 { - let (hw, hh) = (r.w / 2.0, r.h / 2.0); - let qx = libm::fabsf(x - (r.x + hw)) - (hw - radius); - let qy = libm::fabsf(y - (r.y + hh)) - (hh - radius); - let (ox, oy) = (qx.max(0.0), qy.max(0.0)); - let out = if ox > 0.0 && oy > 0.0 { libm::sqrtf(ox * ox + oy * oy) } else { ox.max(oy) }; - out + qx.max(qy).min(0.0) - radius +/// The average of two pixels, all four bytes at once. +fn mid(a: [u8; 4], b: [u8; 4]) -> [u8; 4] { + let (a, b) = (u32::from_le_bytes(a), u32::from_le_bytes(b)); + ((a & b) + (((a ^ b) & 0xfefe_fefe) >> 1)).to_le_bytes() } -/// A soft shadow under `r` (corners rounded by `radius`): cast `down` -/// pixels below it, `dark` (percent) at its edge and gone `spread` out. -/// Only where it shows is touched: what `r` covers is drawn over anyway. -pub fn shadow(pm: &mut [u8], width: usize, r: Rect, radius: f32, down: f32, spread: f32, dark: u8) { - let radius = radius.min(r.w / 2.0).min(r.h / 2.0).max(0.0); - let caster = Rect { y: r.y + down, ..r }; - let around = Rect { x: r.x - spread, y: r.y - spread, w: r.w + 2.0 * spread, h: r.h + down + 2.0 * spread }; - let (cols, rows) = span(around, width, pm.len() / 4 / width.max(1)); - // Well inside `r`: covered by it. - let (in_x, in_y) = (r.x + radius..r.x + r.w - radius, r.y..r.y + r.h); - for y in rows { - let fy = y as f32 + 0.5; - let row = &mut pm[y * width * 4..(y + 1) * width * 4]; - for x in cols.clone() { - let fx = x as f32 + 0.5; - if in_x.contains(&fx) && in_y.contains(&fy) { - continue; - } - let d = outside(caster, radius, fx, fy).max(0.0); - let fade = (1.0 - d / spread).max(0.0); - let k = (fade * fade * dark as f32 * 256.0 / 100.0) as u32; - if k > 0 { - blend(&mut row[x * 4..x * 4 + 4], [0, 0, 0, 255], k); - } +/// `img` at twice the size (cut to `w` by `h`), the new pixels between +/// the old ones the average of their neighbours: the even rows from the +/// source rows, the odd ones from the rows either side. +fn double(img: &Image, w: usize, h: usize) -> Image { + let mut px = vec![[0u8; 4]; w * h]; + for y in (0..h).step_by(2) { + let src = &img.px[(y / 2).min(img.h - 1) * img.w..][..img.w]; + let out = &mut px[y * w..(y + 1) * w]; + for (x, o) in out.iter_mut().enumerate() { + let s = src[(x / 2).min(img.w - 1)]; + *o = if x % 2 == 0 { s } else { mid(s, src[(x / 2 + 1).min(img.w - 1)]) }; } } + for y in (1..h).step_by(2) { + let below = if y + 1 < h { y + 1 } else { y - 1 }; + for x in 0..w { + px[y * w + x] = mid(px[(y - 1) * w + x], px[below * w + x]); + } + } + Image { w, h, px } } /// A box blur run three times over (close to a Gaussian), `r` pixels each -/// way, rows then columns. +/// way: along each row, then down every column at once, so memory is +/// walked in order. fn blur(img: &mut Image, r: usize) { if r == 0 { return; } - let (w, h) = (img.w, img.h); - let mut line = vec![[0u8; 4]; w.max(h)]; + let mut line = vec![[0u8; 4]; img.w]; + let mut sums = vec![[0u32; 4]; img.w]; for _ in 0..3 { - for y in 0..h { - pass(&mut img.px, y * w, 1, w, r, &mut line); - } - for x in 0..w { - pass(&mut img.px, x, w, h, r, &mut line); + for y in 0..img.h { + across(&mut img.px[y * img.w..(y + 1) * img.w], r, &mut line); } + down(img, r, &mut sums); } } -/// One box pass along `n` pixels starting at `start`, `step` apart; the -/// edges are held. -fn pass(px: &mut [[u8; 4]], start: usize, step: usize, n: usize, r: usize, line: &mut [[u8; 4]]) { - let at = |i: usize| start + i.min(n - 1) * step; - let width = (2 * r + 1) as u32; - for c in 0..4 { - // The window centred on pixel 0, the edge held. - let mut sum = px[at(0)][c] as u32 * (r as u32 + 1) + (1..=r).map(|i| px[at(i)][c] as u32).sum::(); - for (i, out) in line.iter_mut().take(n).enumerate() { - out[c] = (sum / width) as u8; - sum = sum + px[at(i + r + 1)][c] as u32 - px[at(i.saturating_sub(r))][c] as u32; +/// Dividing a sum of `2r + 1` bytes by that, as a multiply and a shift: +/// exact for such sums. +fn reciprocal(r: usize) -> u32 { + (65536 + 2 * r as u32) / (2 * r as u32 + 1) +} + +fn add(sum: &mut [u32; 4], p: [u8; 4]) { + sum[0] += p[0] as u32; + sum[1] += p[1] as u32; + sum[2] += p[2] as u32; + sum[3] += p[3] as u32; +} + +fn sub(sum: &mut [u32; 4], p: [u8; 4]) { + sum[0] -= p[0] as u32; + sum[1] -= p[1] as u32; + sum[2] -= p[2] as u32; + sum[3] -= p[3] as u32; +} + +fn average(sum: &[u32; 4], inv: u32) -> [u8; 4] { + [((sum[0] * inv) >> 16) as u8, ((sum[1] * inv) >> 16) as u8, ((sum[2] * inv) >> 16) as u8, ((sum[3] * inv) >> 16) as u8] +} + +/// One pass along `row`, its ends held. +fn across(row: &mut [[u8; 4]], r: usize, line: &mut [[u8; 4]]) { + let n = row.len(); + let line = &mut line[..n]; + line.copy_from_slice(row); + let inv = reciprocal(r); + let mut sum = [0u32; 4]; + for i in 0..=2 * r { + add(&mut sum, line[i.saturating_sub(r).min(n - 1)]); + } + for (i, out) in row.iter_mut().enumerate() { + *out = average(&sum, inv); + add(&mut sum, line[(i + r + 1).min(n - 1)]); + sub(&mut sum, line[i.saturating_sub(r)]); + } +} + +/// One pass down every column, a row at a time; the top and bottom held. +fn down(img: &mut Image, r: usize, sums: &mut [[u32; 4]]) { + let (w, h) = (img.w, img.h); + let src = img.px.clone(); + let row = |i: usize| &src[i.min(h - 1) * w..][..w]; + let inv = reciprocal(r); + sums.fill([0; 4]); + for i in 0..=2 * r { + for (s, p) in sums.iter_mut().zip(row(i.saturating_sub(r))) { + add(s, *p); } } - for (i, p) in line.iter().take(n).enumerate() { - px[start + i * step] = *p; + for y in 0..h { + for (out, s) in img.px[y * w..(y + 1) * w].iter_mut().zip(sums.iter()) { + *out = average(s, inv); + } + for ((s, a), b) in sums.iter_mut().zip(row(y + r + 1)).zip(row(y.saturating_sub(r))) { + add(s, *a); + sub(s, *b); + } } } diff --git a/crates/k-ui/src/lib.rs b/crates/k-ui/src/lib.rs index 0493f20..d309555 100644 --- a/crates/k-ui/src/lib.rs +++ b/crates/k-ui/src/lib.rs @@ -11,6 +11,7 @@ extern crate alloc; pub mod element; pub mod glass; pub mod layout; +mod pixels; pub mod motion; pub mod render; pub mod run; diff --git a/crates/k-ui/src/pixels.rs b/crates/k-ui/src/pixels.rs new file mode 100644 index 0000000..fb6a045 --- /dev/null +++ b/crates/k-ui/src/pixels.rs @@ -0,0 +1,184 @@ +//! K-UI's own pixel loops over a window's bytes (R, G, B, A, premultiplied, +//! `width` pixels a row): tiny-skia's translucent fills, gradients and +//! patterns go through its float pipeline, which is slow wherever float is +//! (QEMU emulates every float operation). Here float is kept to the +//! rounded corners; straight runs share one value a row (or column) and +//! blend in integers, two channels at a time. + +use crate::layout::Rect; +use crate::theme::Rgba; + +/// The pixels of `r` (whole ones) inside a `width` by `height` window: +/// columns and rows. +pub fn span(r: Rect, width: usize, height: usize) -> (core::ops::Range, core::ops::Range) { + let (x0, y0) = (r.x.max(0.0) as usize, r.y.max(0.0) as usize); + let (x1, y1) = (((r.x + r.w).max(0.0) as usize).min(width), ((r.y + r.h).max(0.0) as usize).min(height)); + (x0..x1.max(x0), y0..y1.max(y0)) +} + +pub fn height(pm: &[u8], width: usize) -> usize { + pm.len() / 4 / width.max(1) +} + +/// The columns of `cols` clear of `r`'s rounded corners (radius `radius`): +/// what lies between them. +pub fn between_corners(r: Rect, radius: f32, cols: &core::ops::Range) -> core::ops::Range { + let lo = (libm::ceilf(r.x + radius).max(0.0) as usize).clamp(cols.start, cols.end); + let hi = (libm::floorf(r.x + r.w - radius).max(0.0) as usize).clamp(lo, cols.end); + lo..hi +} + +/// Whether row `y` passes through `r`'s rounded corners. +pub fn corner_row(r: Rect, radius: f32, y: usize) -> bool { + radius > 0.0 && ((y as f32) < r.y + radius || (y as f32 + 1.0) > r.y + r.h - radius) +} + +/// How much of pixel (`x`, `y`) is inside `r` with its corners rounded by +/// `radius`, in 256ths. +pub fn cover(r: Rect, radius: f32, x: usize, y: usize) -> u32 { + let (fx, fy) = (x as f32 + 0.5, y as f32 + 0.5); + let dx = (r.x + radius - fx).max(fx - (r.x + r.w - radius)); + let dy = (r.y + radius - fy).max(fy - (r.y + r.h - radius)); + if dx <= 0.0 || dy <= 0.0 { + return 256; + } + ((radius + 0.5 - libm::sqrtf(dx * dx + dy * dy)).clamp(0.0, 1.0) * 256.0) as u32 +} + +/// The pixel `d` moved toward `s` by `k` 256ths. +pub fn blend(d: &mut [u8; 4], s: [u8; 4], k: u32) { + let (d32, s32) = (u32::from_le_bytes(*d), u32::from_le_bytes(s)); + let (keep, take) = (256 - k, k); + // Red and blue, then green and alpha, each pair in one word. + let rb = ((d32 & 0x00ff_00ff) * keep + (s32 & 0x00ff_00ff) * take) >> 8 & 0x00ff_00ff; + let ga = (((d32 >> 8) & 0x00ff_00ff) * keep + ((s32 >> 8) & 0x00ff_00ff) * take) & 0xff00_ff00; + *d = (rb | ga).to_le_bytes(); +} + +/// Pixels `cols` of row `y` moved toward `s` by `k` 256ths. +pub fn blend_run(pm: &mut [u8], width: usize, y: usize, cols: core::ops::Range, s: [u8; 4], k: u32) { + if k == 0 || cols.is_empty() { + return; + } + let row = &mut pm[(y * width + cols.start) * 4..(y * width + cols.end) * 4]; + for px in row.as_chunks_mut::<4>().0 { + blend(px, s, k); + } +} + +/// `part` of `pm` moved toward `c` by `k` 256ths: a screen fading in. +pub fn veil(pm: &mut [u8], width: usize, part: Rect, c: Rgba, k: u32) { + let (cols, rows) = span(part, width, height(pm, width)); + for y in rows { + blend_run(pm, width, y, cols.clone(), [c.0, c.1, c.2, 255], k); + } +} + +/// The wet highlight: white along the top of `r`, `alpha` (percent) at +/// the edge and gone `depth` pixels down. +pub fn gloss(pm: &mut [u8], width: usize, r: Rect, radius: f32, alpha: u8, depth: f32) { + let radius = radius.min(r.w / 2.0).min(r.h / 2.0).max(0.0); + let (cols, rows) = span(Rect { h: depth.min(r.h), ..r }, width, height(pm, width)); + let mid = between_corners(r, radius, &cols); + let white = [255u8; 4]; + for y in rows { + let fade = 1.0 - (y as f32 + 0.5 - r.y) / depth; + let a = (fade.max(0.0) * alpha as f32 * 256.0 / 100.0) as u32; + let corner = corner_row(r, radius, y); + let straight = if corner { mid.clone() } else { cols.clone() }; + blend_run(pm, width, y, straight, white, a); + if corner { + for x in (cols.start..mid.start).chain(mid.end..cols.end) { + let k = (cover(r, radius, x, y) * a) >> 8; + if k > 0 { + blend_run(pm, width, y, x..x + 1, white, k); + } + } + } + } +} + +/// How dark a shadow is (256ths) `d` pixels outside what casts it. +fn shade(d: f32, spread: f32, dark: u8) -> u32 { + let fade = (1.0 - d.max(0.0) / spread).max(0.0); + (fade * fade * dark as f32 * 256.0 / 100.0) as u32 +} + +/// A soft shadow under `r` (corners rounded by `radius`): cast `down` +/// pixels below it, `dark` (percent) at its edge and gone `spread` out. +/// What `r` covers is left alone: it is drawn over anyway. +pub fn shadow(pm: &mut [u8], width: usize, r: Rect, radius: f32, down: f32, spread: f32, dark: u8) { + let radius = radius.min(r.w / 2.0).min(r.h / 2.0).max(0.0); + let c = Rect { y: r.y + down, ..r }; + let around = Rect { x: r.x - spread, y: r.y - spread, w: r.w + 2.0 * spread, h: r.h + down + 2.0 * spread }; + let (cols, rows) = span(around, width, height(pm, width)); + let (panel, _) = span(r, width, height(pm, width)); + // Where the caster's edges are straight, across and down. + let mid = between_corners(c, radius, &cols); + let (sy0, sy1) = (c.y + radius, c.y + c.h - radius); + let black = [0, 0, 0, 255]; + for y in rows { + let fy = y as f32 + 0.5; + let in_panel = fy >= r.y && fy < r.y + r.h; + // Above or below the caster, where its top and bottom are straight: + // one darkness for the whole run. + let k = shade((c.y - fy).max(fy - (c.y + c.h)), spread, dark); + let segments = match in_panel { + true => [cols.start..panel.start, panel.end..cols.end], + false => [cols.clone(), 0..0], + }; + for seg in segments { + let straight = seg.start.max(mid.start)..seg.end.min(mid.end); + if !in_panel { + blend_run(pm, width, y, straight.clone(), black, k); + } + for x in seg.filter(|x| in_panel || !straight.contains(x)) { + let fx = x as f32 + 0.5; + let d = match fy >= sy0 && fy < sy1 { + // Beside the caster's straight sides. + true => (c.x - fx).max(fx - (c.x + c.w)), + false => { + let dx = (c.x + radius - fx).max(fx - (c.x + c.w - radius)).max(0.0); + let dy = (sy0 - fy).max(fy - sy1).max(0.0); + libm::sqrtf(dx * dx + dy * dy) - radius + } + }; + blend_run(pm, width, y, x..x + 1, black, shade(d, spread, dark)); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn blend_moves_every_channel_by_its_share() { + let mut d = [0, 100, 200, 255]; + blend(&mut d, [255, 0, 100, 255], 128); + assert_eq!(d, [127, 50, 150, 255]); + let mut d = [10, 20, 30, 40]; + blend(&mut d, [99, 99, 99, 99], 0); + assert_eq!(d, [10, 20, 30, 40], "none of it"); + blend(&mut d, [99, 98, 97, 96], 256); + assert_eq!(d, [99, 98, 97, 96], "all of it"); + } + + #[test] + fn a_shadow_darkens_around_and_below_but_not_under_or_far() { + let w = 60; + let mut pm = vec![200u8; w * w * 4]; + let r = Rect { x: 20.0, y: 20.0, w: 20.0, h: 20.0 }; + shadow(&mut pm, w, r, 6.0, 3.0, 7.0, 30); + let red = |x: usize, y: usize| pm[(y * w + x) * 4]; + assert_eq!(red(30, 30), 200, "under the panel: left alone"); + assert!(red(30, 41) < red(30, 18), "darker below than above"); + assert!(red(17, 30) < 200, "beside it"); + assert_eq!(red(5, 5), 200, "far off"); + assert_eq!(red(30, 55), 200, "past its spread"); + } + + extern crate std; + use std::vec; +} diff --git a/crates/k-ui/src/render.rs b/crates/k-ui/src/render.rs index a66559d..bd36916 100644 --- a/crates/k-ui/src/render.rs +++ b/crates/k-ui/src/render.rs @@ -260,13 +260,13 @@ impl Painter<'_, '_> { fn shadow(&mut self, r: Rect) { let width = self.pm.width() as usize; let (down, spread, dark) = SHADOW; - crate::glass::shadow(self.pm.data_mut(), width, r, self.theme.corner_radius, down, spread, dark); + crate::pixels::shadow(self.pm.data_mut(), width, r, self.theme.corner_radius, down, spread, dark); } /// The wet highlight along the top. fn gloss(&mut self, r: Rect) { let width = self.pm.width() as usize; - crate::glass::gloss(self.pm.data_mut(), width, r, self.theme.corner_radius, self.theme.highlight, GLOSS); + crate::pixels::gloss(self.pm.data_mut(), width, r, self.theme.corner_radius, self.theme.highlight, GLOSS); } /// `r` filled with `c`, its corners rounded by `radius`. @@ -308,8 +308,15 @@ pub fn render(pm: &mut PixmapMut, root: &Element, rects: &[Rect], fonts: & p.ring(r); } if frame.enter < 1.0 { - let veil = Rgba(bg.0, bg.1, bg.2, ((1.0 - frame.enter) * 255.0) as u8); - let all = Rect { x: 0.0, y: 0.0, w: p.pm.width() as f32, h: p.pm.height() as f32 }; - p.fill(all, 0.0, veil); + // The new screen's things fade up over the wallpaper, which stays. + let mut leaves = alloc::vec::Vec::new(); + crate::tree::leaf_rects(root, rects, &mut 0, &mut leaves); + if let Some(content) = leaves.into_iter().map(|r| r.grow(REACH)).reduce(Rect::union) { + let (width, k) = (p.pm.width() as usize, ((1.0 - frame.enter) * 256.0) as u32); + match backdrop { + Some(b) => b.fade(p.pm.data_mut(), width, content, k), + None => crate::pixels::veil(p.pm.data_mut(), width, content, bg, k), + } + } } } diff --git a/crates/k-ui/src/tree.rs b/crates/k-ui/src/tree.rs index c94dd3b..91af3ac 100644 --- a/crates/k-ui/src/tree.rs +++ b/crates/k-ui/src/tree.rs @@ -70,7 +70,7 @@ pub fn whole_elements(view: &Element, rects: &[Rect], part: Rect) -> Rect } } -fn leaf_rects(e: &Element, rects: &[Rect], index: &mut usize, out: &mut Vec) { +pub fn leaf_rects(e: &Element, rects: &[Rect], index: &mut usize, out: &mut Vec) { let me = *index; *index += 1; match &e.kind {