srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/decoration/corners.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/wayland/src/decoration/corners.rs')
-rw-r--r--crates/wayland/src/decoration/corners.rs135
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);
}
}
}