srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland
diff options
context:
space:
mode:
Diffstat (limited to 'crates/wayland')
-rw-r--r--crates/wayland/src/decoration/border.rs64
-rw-r--r--crates/wayland/src/decoration/corners.rs135
-rw-r--r--crates/wayland/src/decoration/tests.rs88
-rw-r--r--crates/wayland/src/rounded_corners_pixman.rs2
-rw-r--r--crates/wayland/src/state/lifecycle.rs2
5 files changed, 230 insertions, 61 deletions
diff --git a/crates/wayland/src/decoration/border.rs b/crates/wayland/src/decoration/border.rs
index 9a2ed91..9e29fe6 100644
--- a/crates/wayland/src/decoration/border.rs
+++ b/crates/wayland/src/decoration/border.rs
@@ -4,7 +4,7 @@
//! the caller as plain flat fills (`elements::border_side_render_element`).
use super::color::rgb_to_bgra;
-use super::corners::{round_bottom_corners, round_top_corners};
+use super::corners::{round_bottom_corners, round_top_corners, InnerRing};
pub fn border_strips(geometry: srdwm_core::Rect, width: u32) -> [srdwm_core::Rect; 4] {
let w = width as i32;
@@ -63,7 +63,14 @@ pub fn border_strips(geometry: srdwm_core::Rect, width: u32) -> [srdwm_core::Rec
/// rounding rather than left as solid `color` (which would otherwise paint
/// a solid border-coloured bar over the titlebar's own left/right edges
/// for however many rows this extended by).
-pub fn render_border_top(width: u32, thickness: u32, color: (u8, u8, u8), radius: u32) -> Vec<u8> {
+///
+/// `decorated` picks which of the two things sits underneath this strip's
+/// own "extra" rows - a titlebar band (`decorated = true`) or client
+/// content directly (`decorated = false`) - and therefore which circle
+/// the inner ring needs to trace to meet it with no gap; see
+/// [`InnerRing`]'s own doc comment for why those are two genuinely
+/// different circles, not the same one at two different radii.
+pub fn render_border_top(width: u32, thickness: u32, color: (u8, u8, u8), radius: u32, decorated: bool) -> Vec<u8> {
let (width, thickness) = (width.max(1) as usize, thickness.max(1) as usize);
let height = thickness.max(radius as usize).max(1);
let bg = rgb_to_bgra(color, 255);
@@ -71,14 +78,22 @@ pub fn render_border_top(width: u32, thickness: u32, color: (u8, u8, u8), radius
for px in buf.chunks_exact_mut(4) {
px.copy_from_slice(&bg);
}
- // `Some(radius - thickness)`: without this, the corner stayed a solid
- // filled disk out to the centre column/row instead of a proper ring --
- // see `corners::carve_inner_corner_pixel`'s own doc comment for the
- // full story (reported live as "squares on the inside corners").
- // `None` when `radius <= thickness` - no ring to carve, the corner is
- // already exactly `thickness` px wide at most.
- let inner_radius = (radius as usize > thickness).then(|| radius - thickness as u32);
- round_top_corners(&mut buf, width, height, radius, radius as i32, radius as i32, inner_radius);
+ // Without an inner ring at all, the corner stayed a solid filled disk
+ // out to the centre column/row instead of a proper ring - see
+ // `corners::carve_inner_corner_pixel`'s own doc comment for the full
+ // story (reported live as "squares on the inside corners"). Which
+ // ring depends on `decorated` - see this function's own doc comment
+ // and `InnerRing`'s. `None` when `radius <= thickness`: no ring to
+ // carve, the corner is already exactly `thickness` px wide at most.
+ let inner = (radius as usize > thickness).then(|| {
+ if decorated {
+ InnerRing { center_row: radius as i32, center_col: radius as i32, radius: radius - thickness as u32 }
+ } else {
+ let shifted = radius as i32 + thickness as i32;
+ InnerRing { center_row: shifted, center_col: shifted, radius }
+ }
+ });
+ round_top_corners(&mut buf, width, height, radius, radius as i32, radius as i32, inner);
clip_middle_beyond_thickness(&mut buf, width, radius as usize, thickness..height);
buf
}
@@ -116,6 +131,15 @@ fn clip_middle_beyond_thickness(buf: &mut [u8], width: usize, radius: usize, row
/// fragments`) - the same trade-off `render_border_top`'s own call site
/// already makes and for the same reason: cropping a rounded bitmap's
/// source rect per fragment is real extra work for a strip this thin.
+///
+/// Unlike `render_border_top`, there is no `decorated` parameter here --
+/// this compositor never draws a *bottom* titlebar, so whatever sits under
+/// this strip's own "extra" rows is always client content directly,
+/// decorated window or not (a decorated window still gets its own bottom
+/// two corners content-masked - `RoundedCorners::BOTTOM_ONLY`, not `NONE`
+/// - specifically so this strip has real rounded content to meet). See
+/// [`InnerRing`]'s own doc comment for why that's a different circle from
+/// the titlebar-aligned one `render_border_top` sometimes needs.
pub fn render_border_bottom(width: u32, thickness: u32, color: (u8, u8, u8), radius: u32) -> Vec<u8> {
let (width, thickness) = (width.max(1) as usize, thickness.max(1) as usize);
let height = thickness.max(radius as usize).max(1);
@@ -134,10 +158,22 @@ pub fn render_border_bottom(width: u32, thickness: u32, color: (u8, u8, u8), rad
// real corner. `render_border_top` gets `radius` used unshifted here
// for the same reason it does: this buffer's own outermost row is
// genuinely the true tip of the shape.
- // See `render_border_top`'s own matching comment for why this needs an
- // inner radius too.
- let inner_radius = (radius as usize > thickness).then(|| radius - thickness as u32);
- round_bottom_corners(&mut buf, width, height, radius, inner_radius);
+ //
+ // The inner ring's own centre is content's real mask circle, not this
+ // strip's own outer one at a smaller radius - see `render_border_top`'s
+ // matching `inner` and `InnerRing`'s own doc comment. Content sits
+ // `thickness` rows *above* (smaller row index than) this strip's own
+ // outer centre, since content is inside the ring by `thickness` and
+ // this strip's rows grow *upward* into it (see this function's own
+ // "extra rows sit above" comment on `clip_middle_beyond_thickness`
+ // below) - the mirror image of `render_border_top`'s `+ thickness`
+ // shift, which grows downward instead.
+ let inner = (radius as usize > thickness).then(|| {
+ let center_col = radius as i32 + thickness as i32;
+ let center_row = height as i32 - radius as i32 - thickness as i32;
+ InnerRing { center_row, center_col, radius }
+ });
+ round_bottom_corners(&mut buf, width, height, radius, inner);
// Extra rows sit above the original `thickness`, not below - the
// bottom strip's curve resolves going *up* into content, the mirror of
// the top strip's resolving *down* into it. See
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);
}
}
}
diff --git a/crates/wayland/src/decoration/tests.rs b/crates/wayland/src/decoration/tests.rs
index 4507a05..c8eb9b2 100644
--- a/crates/wayland/src/decoration/tests.rs
+++ b/crates/wayland/src/decoration/tests.rs
@@ -468,7 +468,7 @@ fn border_top_rounds_its_own_top_corners_to_match_the_titlebar() {
// change now depends on.
let color = (0x40, 0x50, 0x60);
let (width, thickness) = (60, 2);
- let buf = render_border_top(width, thickness, color, CORNER_RADIUS);
+ let buf = render_border_top(width, thickness, color, CORNER_RADIUS, true);
let alpha_at = |x: usize, y: usize| buf[(y * width as usize + x) * 4 + 3];
assert_eq!(alpha_at(0, 0), 0, "top-left corner pixel should be clipped");
assert_eq!(alpha_at(width as usize - 1, 0), 0, "top-right corner pixel should be clipped");
@@ -500,7 +500,7 @@ fn border_top_and_titlebar_corners_meet_without_a_seam() {
// immediately below it.
let color = (0x40, 0x50, 0x60);
let (width, thickness, radius) = (60, 4, 6);
- let border = render_border_top(width, thickness, color, radius);
+ let border = render_border_top(width, thickness, color, radius, true);
let titlebar = render_titlebar(width, 24, "", color, (0xff, 0xff, 0xff), true, radius, thickness, true, None, false, false, false, None, true, false);
let border_alpha_at = |x: usize| border[((thickness as usize - 1) * width as usize + x) * 4 + 3];
let titlebar_alpha_at = |xt: usize| titlebar[xt * 4 + 3];
@@ -547,6 +547,86 @@ fn border_top_and_titlebar_corners_meet_without_a_seam() {
}
#[test]
+fn border_top_and_content_mask_have_no_gap_along_the_corner_diagonal_when_undecorated() {
+ // The undecorated counterpart to `border_top_and_titlebar_corners_
+ // meet_without_a_seam`: an undecorated (CSD) window has no titlebar
+ // band under the border's own "extra" rows - client content sits
+ // there directly, masked by `rounded_corners_pixman::apply_corner_
+ // mask` at its own independently-centred circle, not the titlebar's
+ // shared one. `InnerRing`'s own doc comment has the full geometry:
+ // content's mask circle is offset `(thickness, thickness)` from the
+ // border's own outer circle - two same-radius circles with
+ // *different* centres, not one circle at two different radii, so
+ // "which single row/column is the seam" (the titlebar test's own
+ // approach) doesn't carry over cleanly here. What has to hold
+ // instead: at every point *along the diagonal ray* from the true
+ // corner outward (the direction the two circles' centres are
+ // actually offset along, so the worst case for a gap between them),
+ // at least one of the two curves covers it. Verified by hand first
+ // (see this fix's own commit) that the two circles' coverage
+ // actually overlaps continuously along this ray for a real
+ // radius/thickness pair; this checks that promise against the real
+ // rendering functions, not a reimplementation of their math.
+ let color = (0x40, 0x50, 0x60);
+ let (width, thickness, radius) = (60u32, 4u32, 6u32);
+ let border = render_border_top(width, thickness, color, radius, false);
+ let border_h = (thickness as usize).max(radius as usize);
+ // A real content buffer, masked the same way `masked_content_buffer`
+ // masks a window's actual composited surface tree - opaque
+ // premultiplied white everywhere before masking, so alpha alone
+ // tells the whole story.
+ let (cw, ch) = (width - 2 * thickness, radius * 4);
+ let mut content = vec![0u8; (cw * ch * 4) as usize];
+ for px in content.chunks_exact_mut(4) {
+ px.copy_from_slice(&[0xff, 0xff, 0xff, 0xff]);
+ }
+ crate::rounded_corners_pixman::apply_corner_mask(&mut content, cw as i32, ch as i32, cw as i32 * 4, radius as f32, crate::rounded_corners::RoundedCorners::ALL);
+ // Both sampled in the *same* global coordinate frame (border's own
+ // origin is the true corner, `(0, 0)`) - `None` past either
+ // buffer's own edge, same as that position genuinely contributing no
+ // coverage of its own there.
+ let border_alpha_at = |row: i32, col: i32| -> u8 {
+ if row < 0 || col < 0 || row as usize >= border_h || col as usize >= width as usize {
+ return 0;
+ }
+ border[(row as usize * width as usize + col as usize) * 4 + 3]
+ };
+ let content_alpha_at = |row: i32, col: i32| -> u8 {
+ let (r, c) = (row - thickness as i32, col - thickness as i32);
+ if r < 0 || c < 0 || r as usize >= ch as usize || c as usize >= cw as usize {
+ return 0;
+ }
+ content[(r as usize * cw as usize + c as usize) * 4 + 3]
+ };
+ // Every half-pixel step along the 45-degree diagonal from just past
+ // the true corner tip (where both curves are correctly, deliberately
+ // transparent - that IS the rounding) out to comfortably past where
+ // content's own circle takes over full coverage.
+ let mut steps = 0;
+ let mut min_combined = 255u8;
+ let mut min_at = 0.0f32;
+ let mut s = radius as f32 - 1.0;
+ while s <= (radius + thickness + radius) as f32 - 2.0 {
+ let (row, col) = ((s / std::f32::consts::SQRT_2).round() as i32, (s / std::f32::consts::SQRT_2).round() as i32);
+ let combined = border_alpha_at(row, col).max(content_alpha_at(row, col));
+ if combined < min_combined {
+ min_combined = combined;
+ min_at = s;
+ }
+ steps += 1;
+ s += 0.5;
+ }
+ assert!(steps > 10, "sanity: the sampled range should cover more than a couple of points");
+ assert!(min_combined > 160, "diagonal distance {min_at} from the true corner: neither border (alpha={}) nor content (alpha={}) covers this point - a real gap", {
+ let (row, col) = ((min_at / std::f32::consts::SQRT_2).round() as i32, (min_at / std::f32::consts::SQRT_2).round() as i32);
+ border_alpha_at(row, col)
+ }, {
+ let (row, col) = ((min_at / std::f32::consts::SQRT_2).round() as i32, (min_at / std::f32::consts::SQRT_2).round() as i32);
+ content_alpha_at(row, col)
+ });
+}
+
+#[test]
fn border_top_visible_rows_decorated_shows_the_whole_taller_buffer() {
let (row0, rows, shift) = border_top_visible_rows(true, 4, 11);
assert_eq!((row0, rows, shift), (0, 11, 0), "decorated: full max(border_width, radius) buffer, unshifted");
@@ -605,7 +685,7 @@ fn border_top_extra_rows_are_transparent_outside_the_corners() {
// live-confirmed bug, the top-strip half of the pair).
let color = (0x40, 0x50, 0x60);
let (width, thickness, radius) = (60, 2, 6);
- let buf = render_border_top(width, thickness, color, radius);
+ let buf = render_border_top(width, thickness, color, radius, true);
let height = (thickness as usize).max(radius as usize);
assert_eq!(buf.len(), width as usize * height * 4, "buffer must actually be the taller max(thickness, radius) height");
let alpha_at = |x: usize, y: usize| buf[(y * width as usize + x) * 4 + 3];
@@ -651,7 +731,7 @@ fn border_top_curve_actually_closes_within_the_side_strips_own_width() {
// edges can give at their closest approach.
let color = (0x40, 0x50, 0x60);
let (width, thickness, radius) = (60u32, 3u32, 6u32);
- let buf = render_border_top(width, thickness, color, radius);
+ let buf = render_border_top(width, thickness, color, radius, true);
let height = (thickness as usize).max(radius as usize);
let alpha_at = |x: usize, y: usize| buf[(y * width as usize + x) * 4 + 3];
let alpha = alpha_at(thickness as usize - 1, height - 1);
diff --git a/crates/wayland/src/rounded_corners_pixman.rs b/crates/wayland/src/rounded_corners_pixman.rs
index 3a712f9..808d0fe 100644
--- a/crates/wayland/src/rounded_corners_pixman.rs
+++ b/crates/wayland/src/rounded_corners_pixman.rs
@@ -142,7 +142,7 @@ pub(crate) fn masked_content_buffer(renderer: &mut PixmanRenderer, surface: &WlS
/// rather than a per-corner mirrored center, so one formula handles all
/// four boxes correctly regardless of which edges of the image they sit
/// against.
-fn apply_corner_mask(buf: &mut [u8], w: i32, h: i32, stride: i32, radius: f32, corners: RoundedCorners) {
+pub(crate) fn apply_corner_mask(buf: &mut [u8], w: i32, h: i32, stride: i32, radius: f32, corners: RoundedCorners) {
if radius < 1.0 || w <= 0 || h <= 0 {
return;
}
diff --git a/crates/wayland/src/state/lifecycle.rs b/crates/wayland/src/state/lifecycle.rs
index 1de8563..b47cb23 100644
--- a/crates/wayland/src/state/lifecycle.rs
+++ b/crates/wayland/src/state/lifecycle.rs
@@ -203,7 +203,7 @@ impl CompState {
// exactly the extra height.
let strip_h = w.border_width.max(w.corner_radius);
if strips[0].width > 0 && strips[0].height > 0 {
- let data = decoration::render_border_top(strips[0].width, w.border_width, color, w.corner_radius);
+ let data = decoration::render_border_top(strips[0].width, w.border_width, color, w.corner_radius, w.decorated);
let buffer = MemoryRenderBuffer::from_slice(&data, Fourcc::Argb8888, (strips[0].width as i32, strip_h as i32), 1, Transform::Normal, None);
self.border_top_decorations.insert(id, buffer);
} else {