srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/decoration/tests.rs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-05-23 02:31:00 +0200
committersrdusr <[email protected]>2025-05-23 02:31:00 +0200
commit9fb4f996fe76dc5eeee861e463e62fdb6f7de816 (patch)
treea5288ce4ada9a895baa9031e6af1f77a806abe23 /crates/wayland/src/decoration/tests.rs
parentc6bdc26541c4aa8d6594de0ba16a20504c44487a (diff)
downloadsrdwm-9fb4f996fe76dc5eeee861e463e62fdb6f7de816.tar.gz
srdwm-9fb4f996fe76dc5eeee861e463e62fdb6f7de816.zip
Fix the border ring's inner cut using the wrong circle for undecorated windows
carve_inner_corner_pixel (an earlier ring fix) cut the border strip's own disk using an inner circle at the *same* centre as the outer cut, radius - border_width - correct when a titlebar sits underneath (it shares that exact circle, by construction), but wrong whenever client content sits underneath instead: 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 starts border_width rows/columns inside the border strip's - a same-radius circle offset (border_width, border_width) diagonally from the border's own outer one, not a smaller concentric one. Reusing the titlebar-style ring left a real, if very small, gap along part of the seam and a thin sliver of double coverage along the rest - reported live, at extreme zoom against a solid-colour wallpaper (chosen specifically to make a sub-pixel gap easy to spot against, unlike the usual desktop image): "very tiny gaps... corner radius does not match." round_top_corners/round_bottom_corners's own `inner_radius: Option<u32>` parameter is now `inner: Option<InnerRing>`, an explicit (center_row, center_col, radius) rather than an implicit "same centre, smaller radius" - InnerRing's own doc comment has the full geometry for both cases. render_border_top gained a `decorated` parameter to pick the right one (titlebar-aligned when true, content-aligned - centre shifted by border_width on both axes, radius unchanged - when false); render_border_bottom always uses the content-aligned ring, since this compositor never draws a bottom titlebar. apply_corner_mask (rounded_corners_pixman.rs) made pub(crate) so the new regression test can build a real masked content buffer via the actual production function, not a reimplementation of its math. border_top_and_content_mask_have_no_gap_along_the_corner_diagonal_when_undecorated checks coverage along the diagonal ray between the two circles' centres - the direction they're actually offset along, and so the worst case for a gap opening up between them - against the real render_border_top/apply_corner_mask output, not a hand-rederived formula.
Diffstat (limited to 'crates/wayland/src/decoration/tests.rs')
-rw-r--r--crates/wayland/src/decoration/tests.rs88
1 files changed, 84 insertions, 4 deletions
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);