srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/decoration
diff options
context:
space:
mode:
Diffstat (limited to 'crates/wayland/src/decoration')
-rw-r--r--crates/wayland/src/decoration/tests.rs36
-rw-r--r--crates/wayland/src/decoration/titlebar.rs24
2 files changed, 41 insertions, 19 deletions
diff --git a/crates/wayland/src/decoration/tests.rs b/crates/wayland/src/decoration/tests.rs
index f2a9ded..0d6609f 100644
--- a/crates/wayland/src/decoration/tests.rs
+++ b/crates/wayland/src/decoration/tests.rs
@@ -152,7 +152,7 @@ fn shadow_bitmap_corner_is_softer_than_a_square_windows_when_rounded() {
#[test]
fn fills_background_when_no_text() {
- let buf = render_titlebar(40, 20, "", (0x2e, 0x34, 0x40), (0xec, 0xef, 0xf4), true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false);
+ let buf = render_titlebar(40, 20, "", (0x2e, 0x34, 0x40), (0xec, 0xef, 0xf4), true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false, true);
assert_eq!(buf.len(), 40 * 20 * 4);
// Center, not (0,0): the top-left pixel is inside the rounded
// corner `round_top_corners` clips away, so it's transparent by
@@ -175,7 +175,7 @@ fn button_icons_are_drawn_in_the_squares_hit_test_assigns_them() {
let (width, height) = (300u32, srdwm_core::TITLEBAR_HEIGHT);
let bg = (0x2e, 0x34, 0x40);
let fg = (0xec, 0xef, 0xf4);
- let buf = render_titlebar(width, height, "", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false);
+ let buf = render_titlebar(width, height, "", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false, true);
let frame = srdwm_core::Rect::new(0, 0, width, height);
let (width, height) = (width as usize, height as usize);
@@ -189,7 +189,7 @@ fn button_icons_are_drawn_in_the_squares_hit_test_assigns_them() {
let cx = (x0 + x1) / 2;
let cy = (y0 + y1) / 2;
assert_eq!(
- srdwm_core::ResizeEdge::hit_test(frame, cx, cy, true, 0, srdwm_core::RESIZE_MARGIN, false, None, false),
+ srdwm_core::ResizeEdge::hit_test(frame, cx, cy, true, 0, srdwm_core::RESIZE_MARGIN, false, None, false, true),
Some(expected),
"icon drawn at right_offset={right_offset} does not land in the square hit_test assigns to {expected:?}"
);
@@ -208,8 +208,8 @@ fn a_dialog_draws_only_close_and_never_a_coloured_traffic_light() {
// must override it regardless of what the active theme otherwise
// uses everywhere else. `glyph_always = true` so Close's own X is
// visible without needing a live hover to check it landed.
- let dialog = render_titlebar(width, height, "", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, true, None, true, true);
- let normal = render_titlebar(width, height, "", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, true, None, true, false);
+ let dialog = render_titlebar(width, height, "", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, true, None, true, true, true);
+ let normal = render_titlebar(width, height, "", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, true, None, true, false, true);
let (w, h) = (width as usize, height as usize);
// Where a normal (non-dialog) titlebar draws Maximize (offset
@@ -251,7 +251,7 @@ fn drawing_title_changes_some_pixels_when_font_available() {
}
let bg = (0x2e, 0x34, 0x40);
let fg = (0xec, 0xef, 0xf4);
- let buf = render_titlebar(200, 30, "Terminal", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false);
+ let buf = render_titlebar(200, 30, "Terminal", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false, true);
let bg_bytes = rgb_to_bgra(bg, 255);
let changed = buf.chunks_exact(4).any(|px| px != bg_bytes);
assert!(changed, "expected at least one pixel to differ from the background once text is drawn");
@@ -282,8 +282,8 @@ fn centered_title_starts_further_right_than_left_aligned() {
let leftmost_ink_column = |buf: &[u8]| -> Option<usize> {
(0..width as usize).find(|&x| scan_rows.clone().any(|y| buf[(y * width as usize + x) * 4..(y * width as usize + x) * 4 + 4] != bg_bytes))
};
- let left = render_titlebar(width, height, "Hi", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false);
- let centered = render_titlebar(width, height, "Hi", bg, fg, true, CORNER_RADIUS, 0, true, None, true, false, false, None, true, false);
+ let left = render_titlebar(width, height, "Hi", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false, true);
+ let centered = render_titlebar(width, height, "Hi", bg, fg, true, CORNER_RADIUS, 0, true, None, true, false, false, None, true, false, true);
let left_start = leftmost_ink_column(&left).expect("left-aligned title must draw some ink");
let centered_start = leftmost_ink_column(&centered).expect("centered title must draw some ink");
assert!(centered_start > left_start, "a short title centered in a wide titlebar must start well to the right of the left-aligned version (left starts at {left_start}, centered at {centered_start})");
@@ -315,7 +315,7 @@ fn centered_title_ignores_the_button_reservation_and_centers_on_the_whole_width(
let ink_columns = |buf: &[u8]| -> Vec<usize> {
(button_reservation..width as usize).filter(|&x| scan_rows.clone().any(|y| buf[(y * width as usize + x) * 4..(y * width as usize + x) * 4 + 4] != bg_bytes)).collect()
};
- let buf = render_titlebar(width, height, "Hi", bg, fg, true, CORNER_RADIUS, 0, true, None, true, true, false, None, true, false);
+ let buf = render_titlebar(width, height, "Hi", bg, fg, true, CORNER_RADIUS, 0, true, None, true, true, false, None, true, false, true);
let columns = ink_columns(&buf);
let (first, last) = (*columns.first().expect("centered title must draw some ink"), *columns.last().unwrap());
let midpoint = (first + last) as f32 / 2.0;
@@ -327,7 +327,7 @@ fn centered_title_ignores_the_button_reservation_and_centers_on_the_whole_width(
fn empty_title_leaves_buffer_all_background_outside_the_rounded_corners() {
let bg = (0x10, 0x20, 0x30);
let (width, height) = (50, 24);
- let buf = render_titlebar(width, height, "", bg, (0xff, 0xff, 0xff), true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false);
+ let buf = render_titlebar(width, height, "", bg, (0xff, 0xff, 0xff), true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false, true);
let bg_bytes = rgb_to_bgra(bg, 255);
for (i, px) in buf.chunks_exact(4).enumerate() {
let (x, y) = (i % width as usize, i / width as usize);
@@ -343,7 +343,7 @@ fn empty_title_leaves_buffer_all_background_outside_the_rounded_corners() {
fn corners_are_clipped_but_the_middle_is_not() {
let bg = (0x10, 0x20, 0x30);
let (width, height) = (50, 24);
- let buf = render_titlebar(width, height, "", bg, (0xff, 0xff, 0xff), true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false);
+ let buf = render_titlebar(width, height, "", bg, (0xff, 0xff, 0xff), true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false, true);
let alpha_at = |x: usize, y: usize| buf[(y * width as usize + x) * 4 + 3];
// The very corner pixel is well outside the quarter-circle at any
// sane radius - fully clipped.
@@ -374,7 +374,7 @@ fn clipped_corner_pixels_are_fully_premultiplied_zero_not_just_alpha() {
// is `(0, 0, 0, 0)` in every channel, not just alpha.
let bg = (0x10, 0x20, 0x30);
let (width, height) = (50, 24);
- let buf = render_titlebar(width, height, "", bg, (0xff, 0xff, 0xff), true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false);
+ let buf = render_titlebar(width, height, "", bg, (0xff, 0xff, 0xff), true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false, true);
let px_at = |x: usize, y: usize| &buf[(y * width as usize + x) * 4..(y * width as usize + x) * 4 + 4];
assert_eq!(px_at(0, 0), [0, 0, 0, 0], "top-left corner pixel must be fully zeroed (premultiplied transparent), not just alpha");
assert_eq!(px_at(width as usize - 1, 0), [0, 0, 0, 0], "top-right corner pixel must be fully zeroed (premultiplied transparent), not just alpha");
@@ -384,7 +384,7 @@ fn clipped_corner_pixels_are_fully_premultiplied_zero_not_just_alpha() {
fn round_corners_false_leaves_the_top_corners_square() {
let bg = (0x10, 0x20, 0x30);
let (width, height) = (50, 24);
- let buf = render_titlebar(width, height, "", bg, (0xff, 0xff, 0xff), false, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false);
+ let buf = render_titlebar(width, height, "", bg, (0xff, 0xff, 0xff), false, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false, true);
let alpha_at = |x: usize, y: usize| buf[(y * width as usize + x) * 4 + 3];
assert_eq!(alpha_at(0, 0), 255, "top-left corner should stay square when round_corners is false");
assert_eq!(alpha_at(width as usize - 1, 0), 255, "top-right corner should stay square when round_corners is false");
@@ -406,8 +406,8 @@ fn hovering_the_close_button_brightens_only_that_dot() {
let (width, height) = (200u32, srdwm_core::TITLEBAR_HEIGHT);
let bg = (0x2e, 0x34, 0x40);
let fg = (0xec, 0xef, 0xf4);
- let plain = render_titlebar(width, height, "", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false);
- let close_hovered = render_titlebar(width, height, "", bg, fg, true, CORNER_RADIUS, 0, true, Some((srdwm_core::TitlebarHit::Close, 255)), false, false, false, None, true, false);
+ let plain = render_titlebar(width, height, "", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false, true);
+ let close_hovered = render_titlebar(width, height, "", bg, fg, true, CORNER_RADIUS, 0, true, Some((srdwm_core::TitlebarHit::Close, 255)), false, false, false, None, true, false, true);
let frame = srdwm_core::Rect::new(0, 0, width, height);
let (w, h) = (width as usize, height as usize);
let margin = srdwm_core::BUTTON_CLUSTER_MARGIN as usize;
@@ -424,7 +424,7 @@ fn hovering_the_close_button_brightens_only_that_dot() {
// point to Close, or this test would be checking a hover state
// that a real pointer could never reach in the first place.
let (cx, cy) = ((close_box.0 + close_box.2) / 2, (close_box.1 + close_box.3) / 2);
- assert_eq!(srdwm_core::ResizeEdge::hit_test(frame, cx, cy, true, 0, srdwm_core::RESIZE_MARGIN, false, None, false), Some(srdwm_core::TitlebarHit::Close));
+ assert_eq!(srdwm_core::ResizeEdge::hit_test(frame, cx, cy, true, 0, srdwm_core::RESIZE_MARGIN, false, None, false, true), Some(srdwm_core::TitlebarHit::Close));
}
#[test]
@@ -439,7 +439,7 @@ fn button_dot_has_a_glossy_highlight_toward_the_upper_left_and_shadow_toward_the
let (width, height) = (200u32, srdwm_core::TITLEBAR_HEIGHT);
let bg = (0x2e, 0x34, 0x40);
let fg = (0xec, 0xef, 0xf4);
- let buf = render_titlebar(width, height, "", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false);
+ let buf = render_titlebar(width, height, "", bg, fg, true, CORNER_RADIUS, 0, true, None, false, false, false, None, true, false, true);
let (w, h) = (width as usize, height as usize);
let close_box = button_box(w, h, srdwm_core::BUTTON_CLUSTER_MARGIN as usize, false, BUTTON_MARGIN);
let (cx, cy) = ((close_box.0 + close_box.2) / 2, (close_box.1 + close_box.3) / 2);
@@ -501,7 +501,7 @@ fn border_top_and_titlebar_corners_meet_without_a_seam() {
let color = (0x40, 0x50, 0x60);
let (width, thickness, radius) = (60, 4, 6);
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 titlebar = render_titlebar(width, 24, "", color, (0xff, 0xff, 0xff), true, radius, thickness, true, None, false, false, false, None, true, false, true);
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];
// `x` below is the shared *global* column - distance from the true
diff --git a/crates/wayland/src/decoration/titlebar.rs b/crates/wayland/src/decoration/titlebar.rs
index 03a614a..846bd41 100644
--- a/crates/wayland/src/decoration/titlebar.rs
+++ b/crates/wayland/src/decoration/titlebar.rs
@@ -76,6 +76,13 @@ pub fn render_titlebar(
// the same "renders on one side, hit-tests on the other" trap every
// other button-geometry value here already has to avoid.
is_dialog: bool,
+ // Whether a Maximize button is drawn at all - `WindowManager::
+ // show_maximize`'s answer, which resolves `theme.dynamic_buttons`
+ // against the window's own `resizable`. Must stay in exact agreement
+ // with `ResizeEdge::hit_test`'s own `show_maximize` parameter, the
+ // same "renders on one side, hit-tests on the other" contract
+ // `is_dialog` directly above already carries.
+ show_maximize: bool,
) -> Vec<u8> {
let (width, height) = (width.max(1) as usize, height.max(1) as usize);
// Forced off, not just defaulted - a dialog never gets coloured
@@ -98,7 +105,13 @@ pub fn render_titlebar(
let cluster_margin = srdwm_core::BUTTON_CLUSTER_MARGIN as usize;
// A dialog only ever gets one button (Close) - see this function's own
// `is_dialog` doc comment.
- let wanted_buttons = if is_dialog { 1 } else { 3 };
+ let wanted_buttons = if is_dialog {
+ 1
+ } else if show_maximize {
+ 3
+ } else {
+ 2
+ };
let button_count = if width >= cluster_margin + pitch * wanted_buttons { wanted_buttons } else { 0 };
// `BUTTON_CLUSTER_MARGIN` included, not just the buttons' own `pitch *
// button_count` span - the cluster's own leading gap needs reserving
@@ -245,6 +258,15 @@ pub fn render_titlebar(
[srdwm_core::TitlebarButton::Close, srdwm_core::TitlebarButton::Maximize, srdwm_core::TitlebarButton::Minimize]
})
};
+ // Maximize removed from the list rather than skipped in the loop:
+ // skipping would leave an empty slot where it used to be, while
+ // `hit_test` closes the gap - so every later button would be drawn
+ // one pitch away from where its clicks actually land.
+ let order: Vec<srdwm_core::TitlebarButton> = if show_maximize {
+ order.to_vec()
+ } else {
+ order.iter().copied().filter(|b| *b != srdwm_core::TitlebarButton::Maximize).collect()
+ };
// `BUTTON_CLUSTER_MARGIN` first, then each button's own `pitch * i`
// spacing after it - must stay in agreement with `ResizeEdge::
// hit_test`'s matching `left`/`right` base, the same "renders on