diff options
| -rw-r--r-- | crates/wayland/src/decoration/border.rs | 64 | ||||
| -rw-r--r-- | crates/wayland/src/decoration/corners.rs | 135 | ||||
| -rw-r--r-- | crates/wayland/src/decoration/tests.rs | 88 | ||||
| -rw-r--r-- | crates/wayland/src/rounded_corners_pixman.rs | 2 | ||||
| -rw-r--r-- | crates/wayland/src/state/lifecycle.rs | 2 |
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 { |