srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/decoration.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/wayland/src/decoration.rs')
-rw-r--r--crates/wayland/src/decoration.rs75
1 files changed, 72 insertions, 3 deletions
diff --git a/crates/wayland/src/decoration.rs b/crates/wayland/src/decoration.rs
index 2888d69..42d3231 100644
--- a/crates/wayland/src/decoration.rs
+++ b/crates/wayland/src/decoration.rs
@@ -276,9 +276,13 @@ fn edge_distance(pos: u32, margin: u32, extent: u32) -> u32 {
/// too, at a radius `width` pixels larger than the titlebar's (so the cut
/// continues outward from the titlebar's own, rather than starting over),
/// is what makes a bordered window's corner read as one continuous curve
-/// instead of a rounded titlebar sitting inside a square frame. The other
-/// three strips (bottom/left/right) don't participate in any visible
-/// corner and stay plain solid fills - see their render call sites.
+/// instead of a rounded titlebar sitting inside a square frame.
+///
+/// [`render_border_bottom`] gives the bottom strip the matching treatment
+/// for its own two corners. The left/right strips don't participate in any
+/// visible corner at all (`border_strips`' geometry has them span only the
+/// height *between* the top and bottom strips) and stay plain solid fills
+/// - see their render call sites.
pub fn render_border_top(width: u32, thickness: u32, color: (u8, u8, u8)) -> Vec<u8> {
let (width, thickness) = (width.max(1) as usize, thickness.max(1) as usize);
let bg = rgb_to_bgra(color, 255);
@@ -290,6 +294,29 @@ pub fn render_border_top(width: u32, thickness: u32, color: (u8, u8, u8)) -> Vec
buf
}
+/// [`render_border_top`]'s mirror for the bottom strip - same construction,
+/// its own two corners (bottom-left/bottom-right) cut instead. Reported
+/// live, alongside the top-corner work: a bordered window's bottom two
+/// corners still read as square next to the now-rounded top ones, the same
+/// "inconsistently square" complaint that motivated rounding the top strip
+/// in the first place.
+///
+/// Handled as one all-or-nothing bitmap rather than folded into the
+/// left/right strips' per-fragment occlusion splitting (`visible_border_
+/// 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.
+pub fn render_border_bottom(width: u32, thickness: u32, color: (u8, u8, u8)) -> Vec<u8> {
+ let (width, thickness) = (width.max(1) as usize, thickness.max(1) as usize);
+ let bg = rgb_to_bgra(color, 255);
+ let mut buf = vec![0u8; width * thickness * 4];
+ for px in buf.chunks_exact_mut(4) {
+ px.copy_from_slice(&bg);
+ }
+ round_bottom_corners(&mut buf, width, thickness, CORNER_RADIUS + thickness as u32);
+ buf
+}
+
/// Renders a `width x height` BGRA8 buffer: filled with `background`, with
/// `title` drawn left-aligned in `foreground` (best-effort glyph layout --
/// no text shaping/kerning, adequate for the ASCII-heavy titles window
@@ -403,6 +430,37 @@ fn round_top_corners(buf: &mut [u8], width: usize, height: usize, radius: u32) {
}
}
+/// [`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.
+fn round_bottom_corners(buf: &mut [u8], width: usize, height: usize, radius: u32) {
+ let r = (radius as usize).min(width / 2).min(height);
+ if r == 0 {
+ return;
+ }
+ // `cy` as a signed offset, not a `usize` - `height - r` can be exactly
+ // `0` (a strip whose radius clamp landed on its own full height, same
+ // as `round_top_corners` allows for `r == height`), which would
+ // underflow a plain `usize` subtraction one step further below.
+ let is_outside_corner = |x: usize, y: usize, cx: usize, cy: i64| -> bool {
+ let (dx, dy) = (x as i64 - cx as i64, y as i64 - cy);
+ (dx * dx + dy * dy) as u64 > (r * r) as u64
+ };
+ let cy = height as i64 - r as i64 - 1;
+ for y in (height - r)..height {
+ for x in 0..r {
+ if is_outside_corner(x, y, r, cy) {
+ buf[(y * width + x) * 4 + 3] = 0;
+ }
+ }
+ for x in (width - r)..width {
+ if is_outside_corner(x, y, width - r - 1, cy) {
+ buf[(y * width + x) * 4 + 3] = 0;
+ }
+ }
+ }
+}
+
/// Sets one pixel to `color` if it falls inside the buffer - every icon
/// drawn below goes through this so none of them need their own bounds
/// checks.
@@ -687,6 +745,17 @@ mod tests {
}
#[test]
+ fn border_bottom_rounds_its_own_bottom_corners() {
+ let color = (0x40, 0x50, 0x60);
+ let (width, thickness) = (60, 2);
+ let buf = render_border_bottom(width, thickness, color);
+ let alpha_at = |x: usize, y: usize| buf[(y * width as usize + x) * 4 + 3];
+ assert_eq!(alpha_at(0, thickness as usize - 1), 0, "bottom-left corner pixel should be clipped");
+ assert_eq!(alpha_at(width as usize - 1, thickness as usize - 1), 0, "bottom-right corner pixel should be clipped");
+ assert_eq!(alpha_at(width as usize / 2, 0), 255, "centre of the strip must stay opaque");
+ }
+
+ #[test]
fn context_menu_is_one_row_tall_per_item() {
let items = [("Minimize", false), ("Maximize", false), ("Always on Top", false), ("Close", false)];
let buf = render_context_menu(160, 28, &items, (0x2e, 0x34, 0x40), (0xff, 0xff, 0xff), (0x4c, 0x56, 0x6a), (0x10, 0x10, 0x10));