diff options
Diffstat (limited to 'crates/wayland/src/decoration/corners.rs')
| -rw-r--r-- | crates/wayland/src/decoration/corners.rs | 135 |
1 files changed, 94 insertions, 41 deletions
diff --git a/crates/wayland/src/decoration/corners.rs b/crates/wayland/src/decoration/corners.rs index aeac076..d3ec611 100644 --- a/crates/wayland/src/decoration/corners.rs +++ b/crates/wayland/src/decoration/corners.rs @@ -84,28 +84,39 @@ /// `center_row`'s own default), so this is additive, not a behaviour /// change for border's own corner. /// -/// `inner_radius`, when `Some`, also carves this corner into a proper ring -/// - see [`carve_inner_corner_pixel`]'s own doc comment for why that's -/// needed at all. Only `render_border_top` passes one (`radius - -/// border_width`, the ring's real visible thickness); every other caller -/// (a titlebar's own corner, the lock-screen box) passes `None` and keeps -/// today's solid-disk-past-the-nominal-edge behaviour, which is correct -/// for a single flat-coloured panel with nothing of a *different* colour -/// underneath it needing to show through. -pub(crate) fn round_top_corners(buf: &mut [u8], width: usize, height: usize, radius: u32, center_row: i32, center_col: i32, inner_radius: Option<u32>) { +/// `inner`, when `Some`, also carves this corner into a proper ring - see +/// [`carve_inner_corner_pixel`]'s own doc comment for why that's needed at +/// all, and [`InnerRing`]'s own doc comment for why it needs a centre +/// that's independent of `center_row`/`center_col`, not just a smaller +/// radius at the same one. Every caller that isn't `render_border_top`/ +/// `render_border_bottom` (a titlebar's own corner, the lock-screen box) +/// passes `None` and keeps today's solid-disk-past-the-nominal-edge +/// behaviour, which is correct for a single flat-coloured panel with +/// nothing of a *different* colour underneath it needing to show through. +pub(crate) fn round_top_corners(buf: &mut [u8], width: usize, height: usize, radius: u32, center_row: i32, center_col: i32, inner: Option<InnerRing>) { let r = (radius as usize).min(width / 2); if r == 0 { return; } let rf = r as f32; let cy = center_row as f32; - // How far `center_col` sits from the unshifted default (`radius`) -- - // the right corner's own centre needs shifting by the same amount, in - // the opposite direction (further *into* the buffer from the right - // edge, mirroring how the left corner shifts further *into* it from - // the left), since the buffer's own right edge is the mirror image of - // its left one, not an independent second true edge. - let col_inset = radius as i32 - center_col; + // How far a given centre column sits from the unshifted default + // (`radius`) - the right corner's own centre needs shifting by the + // same amount, in the opposite direction (further *into* the buffer + // from the right edge, mirroring how the left corner shifts further + // *into* it from the left), since the buffer's own right edge is the + // mirror image of its left one, not an independent second true edge. + // A closure, not a one-off `let`, since the inner ring's own centre + // (potentially different from `center_col`) needs the identical + // mirroring, not just the outer cut's. + let mirror_col = |col: i32| (width - r) as f32 + (radius as i32 - col) as f32; + // `> 0`, not just `.is_some()`: a ring whose own radius would be zero + // or negative (an unusually thick border relative to its corner + // radius, for the titlebar-aligned case) has no ring to carve at all + // - the whole disk out to `radius` already *is* the intended + // thickness, and a zero/negative inner radius would carve away the + // entire corner instead of nothing. + let inner = inner.filter(|ring| ring.radius > 0); // Only rows that could plausibly need blending at all: below // `center_row` (this buffer's slice of the circle, whatever portion // of it falls within `[0, height)`) is where the actual curve lives; @@ -121,17 +132,11 @@ pub(crate) fn round_top_corners(buf: &mut [u8], width: usize, height: usize, rad // generalised to an arbitrary `center_row`. let y_lo = (center_row - r as i32).max(0) as usize; let y_hi = (center_row.max(0) as usize).min(height); - // `> 0`, not just `.is_some()`: a `radius <= border_width` window - // (an unusually thick border relative to its corner radius) has no - // ring to carve at all - the whole disk out to `radius` already *is* - // the intended `border_width`-ish thickness, and an inner radius of - // zero or less would carve away the entire corner instead of nothing. - let inner_rf = inner_radius.filter(|&r| r > 0).map(|r| r as f32); for y in y_lo..y_hi { for x in 0..r { blend_corner_pixel(buf, width, x, y, center_col as f32, cy, rf); - if let Some(inner_rf) = inner_rf { - carve_inner_corner_pixel(buf, width, x, y, center_col as f32, cy, inner_rf); + if let Some(ring) = &inner { + carve_inner_corner_pixel(buf, width, x, y, ring.center_col as f32, ring.center_row as f32, ring.radius as f32); } } for x in (width - r)..width { @@ -141,18 +146,58 @@ pub(crate) fn round_top_corners(buf: &mut [u8], width: usize, height: usize, rad // right corner's centre column lines up with `rounded_corners_ // pixman.rs`'s `apply_corner_mask` (`px.clamp(radius, wf - // radius)`, which clamps to exactly `w - r` here) without it. - // `+ col_inset`: the same horizontal shift `center_col` applies - // to the left corner, mirrored - see this function's own doc - // comment on `center_col`/`col_inset`. - let cx = (width - r) as f32 + col_inset as f32; + let cx = mirror_col(center_col); blend_corner_pixel(buf, width, x, y, cx, cy, rf); - if let Some(inner_rf) = inner_rf { - carve_inner_corner_pixel(buf, width, x, y, cx, cy, inner_rf); + if let Some(ring) = &inner { + carve_inner_corner_pixel(buf, width, x, y, mirror_col(ring.center_col), ring.center_row as f32, ring.radius as f32); } } } } +/// The inner ring [`round_top_corners`]/[`round_bottom_corners`] carve into +/// their own outer disk - see [`carve_inner_corner_pixel`]'s own doc +/// comment for why a ring, not a filled disk, is what a border strip's +/// corner actually needs to look like. +/// +/// A *separate* centre from the outer cut's `center_row`/`center_col`, not +/// just a smaller radius at the same one - because what the inner cut +/// needs to reveal differs by what's actually drawn underneath this +/// specific strip's own "extra" rows: +/// +/// - A **titlebar** band underneath (`render_border_top`, `decorated`): +/// the titlebar draws its *own* independently rounded corner, sharing +/// the exact same circle as the border's own outer cut (`center_row`/ +/// `center_col`'s own doc comment on the `radius - border_width` shift +/// that lines the two buffers' circles up). The inner ring here should +/// match *that* circle exactly - same centre as the outer cut, radius +/// `radius - border_width`. +/// - **Client content** underneath (`render_border_top` when undecorated, +/// `render_border_bottom` always - there is no "bottom titlebar" in +/// this compositor's design): content's own rounded-corner mask +/// (`rounded_corners_pixman.rs`'s `apply_corner_mask`) is centred +/// `radius` from *its own* buffer's edges, and that buffer's own origin +/// sits `border_width` rows/columns inside this strip's - a genuinely +/// *different* circle from the border's own outer one, offset by +/// `(border_width, border_width)` diagonally, not just a smaller +/// concentric one. Reusing the titlebar-style "same centre, smaller +/// radius" ring here left a real, visible gap along part of the seam +/// and a thin sliver of double coverage along the rest - both curves +/// are radius-`radius` circles, but centred `border_width` apart, so no +/// single concentric ring traces both correctly. Confirmed live at +/// extreme zoom against a solid-colour wallpaper (easier to spot a +/// sub-pixel-scale gap against than the usual desktop image): a very +/// thin wedge of wallpaper visible right at the point the two circles' +/// radii diverge most. Matching content's own circle exactly -- +/// `radius` unchanged, centre shifted `border_width` further into the +/// buffer on both axes - traces the *same* curve content's own mask +/// already cuts to, so the two meet with no gap and no overlap. +pub(crate) struct InnerRing { + pub(crate) center_row: i32, + pub(crate) center_col: i32, + pub(crate) radius: u32, +} + /// Multiplies the pixel at `(x, y)` by a smoothed 0..1 mask based on its /// distance from `(cx, cy)` versus `radius` - `1` (unchanged) well inside /// the circle, `0` (fully transparent) well outside it, blended over a ~2px @@ -262,10 +307,15 @@ fn carve_inner_corner_pixel(buf: &mut [u8], width: usize, x: usize, y: usize, cx /// [`round_top_corners`]'s mirror for the bottom two corners - same /// construction, corner centres `r` *up* from the bottom instead of down /// from the top. Same anti-aliasing, same reason - see -/// [`blend_corner_pixel`]'s own doc comment. `inner_radius` is the same -/// idea as `round_top_corners`' own parameter of the same name - see its -/// doc comment. -pub(crate) fn round_bottom_corners(buf: &mut [u8], width: usize, height: usize, radius: u32, inner_radius: Option<u32>) { +/// [`blend_corner_pixel`]'s own doc comment. `inner` is the same idea as +/// [`round_top_corners`]' own parameter of the same name - see +/// [`InnerRing`]'s own doc comment. Unlike `round_top_corners`, this +/// function's own outer cut has no `center_row`/`center_col` of its own to +/// default the ring's centre from (nothing has ever needed to shift the +/// *outer* cut here - only `render_border_top`'s own top strip sits above +/// a titlebar that needs one), so `InnerRing`'s fields are the ring's own +/// absolute centre, not an offset from anything. +pub(crate) fn round_bottom_corners(buf: &mut [u8], width: usize, height: usize, radius: u32, inner: Option<InnerRing>) { let r = (radius as usize).min(width / 2); if r == 0 { return; @@ -288,14 +338,17 @@ pub(crate) fn round_bottom_corners(buf: &mut [u8], width: usize, height: usize, // pixman.rs`'s own bottom-box centre (`py.clamp(radius, hf - radius)`, // which clamps to exactly `h - r`) without it. let cy = height as f32 - rf; - // See `round_top_corners`'s matching line for why this is `.filter(|&r| - // r > 0)`, not just `.is_some()`. - let inner_rf = inner_radius.filter(|&r| r > 0).map(|r| r as f32); + // See `round_top_corners`'s own `mirror_col` for why this needs to be a + // closure, not a one-off `let`. + let mirror_col = |col: i32| (width - r) as f32 + (radius as i32 - col) as f32; + // See `round_top_corners`'s matching line for why this is `.filter(|ring| + // ring.radius > 0)`, not just `.is_some()`. + let inner = inner.filter(|ring| ring.radius > 0); for y in (height - rows)..height { for x in 0..r { blend_corner_pixel(buf, width, x, y, rf, cy, rf); - if let Some(inner_rf) = inner_rf { - carve_inner_corner_pixel(buf, width, x, y, rf, cy, inner_rf); + if let Some(ring) = &inner { + carve_inner_corner_pixel(buf, width, x, y, ring.center_col as f32, ring.center_row as f32, ring.radius as f32); } } for x in (width - r)..width { @@ -303,8 +356,8 @@ pub(crate) fn round_bottom_corners(buf: &mut [u8], width: usize, height: usize, // `width - r`, not `width - r - 1`. let cx = (width - r) as f32; blend_corner_pixel(buf, width, x, y, cx, cy, rf); - if let Some(inner_rf) = inner_rf { - carve_inner_corner_pixel(buf, width, x, y, cx, cy, inner_rf); + if let Some(ring) = &inner { + carve_inner_corner_pixel(buf, width, x, y, mirror_col(ring.center_col), ring.center_row as f32, ring.radius as f32); } } } |