srdusr
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crates/wayland/src/decoration.rs2
-rw-r--r--crates/wayland/src/decoration/font.rs12
-rw-r--r--crates/wayland/src/state/desktop_icons.rs11
-rw-r--r--crates/wayland/src/state/lifecycle.rs49
-rw-r--r--crates/wayland/src/state/menu.rs18
-rw-r--r--crates/wayland/src/state/mod.rs21
-rw-r--r--docs/TODO.md18
7 files changed, 108 insertions, 23 deletions
diff --git a/crates/wayland/src/decoration.rs b/crates/wayland/src/decoration.rs
index d24c035..49cf416 100644
--- a/crates/wayland/src/decoration.rs
+++ b/crates/wayland/src/decoration.rs
@@ -42,7 +42,7 @@ pub(crate) use border::{border_bottom_visible_rows, border_top_visible_rows};
pub(crate) use buttons::HOVER_GLYPH_DURATION;
pub(crate) use color::{mix_rgb, rgb_to_bgra};
pub(crate) use corners::{round_bottom_corners, round_top_corners};
-pub(crate) use font::{blit_glyph, find_system_font, FONT_PIXELS, TEXT_LEFT_PADDING};
+pub(crate) use font::{blit_glyph, find_system_font, measure_text_width, FONT_PIXELS, TEXT_LEFT_PADDING};
pub use shadow::{shadow_bitmap, shadow_rect};
pub(crate) use shadow::{SHADOW_MAX_ALPHA, SHADOW_SIZE};
pub use titlebar::render_titlebar;
diff --git a/crates/wayland/src/decoration/font.rs b/crates/wayland/src/decoration/font.rs
index 27df347..6a2a312 100644
--- a/crates/wayland/src/decoration/font.rs
+++ b/crates/wayland/src/decoration/font.rs
@@ -19,6 +19,18 @@ pub(crate) fn find_system_font() -> Option<Font> {
FONT.get_or_init(load_any_monospace_font).clone()
}
+/// The real rendered pixel width of `text` at `size`, in this font - the
+/// same two-pass "sum every glyph's own advance width" measurement `render_
+/// header_box`'s `draw_centered` already does inline, pulled out here so a
+/// caller that needs to *size a box* around text (not just draw it) has one
+/// place to ask, rather than repeating the sum. Returns `0.0` with no
+/// system font found, matching every other text-rendering path's own
+/// "solid colour only, no text at all" fallback in that case.
+pub(crate) fn measure_text_width(font: &Option<Font>, text: &str, size: f32) -> f32 {
+ let Some(font) = font else { return 0.0 };
+ text.chars().filter(|c| !c.is_control()).map(|ch| font.rasterize(ch, size).0.advance_width).sum()
+}
+
fn load_any_monospace_font() -> Option<Font> {
let roots = ["/usr/share/fonts", "/usr/local/share/fonts"];
let mut home_roots = Vec::new();
diff --git a/crates/wayland/src/state/desktop_icons.rs b/crates/wayland/src/state/desktop_icons.rs
index 9d2dd91..05bd85a 100644
--- a/crates/wayland/src/state/desktop_icons.rs
+++ b/crates/wayland/src/state/desktop_icons.rs
@@ -591,8 +591,17 @@ impl CompState {
self.build_desktop_menu_buffer(menu);
}
- fn build_desktop_menu_buffer(&mut self, menu: DesktopMenu) {
+ fn build_desktop_menu_buffer(&mut self, mut menu: DesktopMenu) {
let theme = self.wm.borrow().theme;
+ // Same fixed-width-guess bug the titlebar menu was reported for
+ // ("text goes out of view") - "Open in File Manager"/"New Text
+ // Document" both run past a fixed 170px guess. Widened to this
+ // menu's own widest real label, same measurement `open_context_
+ // menu` (`state/menu.rs`) uses.
+ let font = decoration::find_system_font();
+ let widest_label = menu.items.iter().map(|(label, _)| decoration::measure_text_width(&font, label, decoration::FONT_PIXELS)).fold(0.0_f32, f32::max);
+ let content_width = (widest_label + decoration::TEXT_LEFT_PADDING * 2.0).ceil() as u32;
+ menu.width = menu.width.max(content_width);
// Not redesigned the way the titlebar menu was (`srdwm_core::
// context_menu`'s own module doc comment) - this menu's rows are
// still all one uniform height, `Separator` included, matching
diff --git a/crates/wayland/src/state/lifecycle.rs b/crates/wayland/src/state/lifecycle.rs
index 112c64e..51297bb 100644
--- a/crates/wayland/src/state/lifecycle.rs
+++ b/crates/wayland/src/state/lifecycle.rs
@@ -145,6 +145,17 @@ impl CompState {
// nothing this function reads has actually changed since the last
// call turns those redundant calls into a cheap signature
// comparison instead of a full re-rasterization.
+ // Whether this window is *actually* occupying a tiled slot right
+ // now - see the shadow gate further down for the full reasoning.
+ // Computed once, here, and reused there rather than recomputed:
+ // it depends on the workspace's own layout name, not just this
+ // window's own `floating` flag, so it has to be part of the
+ // signature too - switching a workspace's layout (`Super+Shift+
+ // t`/`s`) changes this for every window on it without touching
+ // any of their own `floating` fields, and a signature that didn't
+ // track it would keep serving whichever shadow state was cached
+ // before the switch.
+ let currently_tiled = self.wm.borrow().workspace(w.workspace).is_some_and(|ws| ws.layout == "tiling") && !w.floating;
let signature = DecorationSignature {
width: frame.width,
height: frame.height,
@@ -157,7 +168,7 @@ impl CompState {
maximized: w.maximized,
fullscreen: w.fullscreen,
shadows_enabled: self.wm.borrow().shadows_enabled,
- floating: w.floating,
+ currently_tiled,
hovered_button,
title_centered: theme.title_centered,
buttons_left: theme.buttons_left,
@@ -252,20 +263,36 @@ impl CompState {
// Hyprland/GNOME convention `MISSING.md` measures this compositor
// against.
//
- // No shadow for a TILED window either - a real, reported bug, not
- // a style choice made up front: a drop shadow exists to separate a
- // window from whatever is visually *behind* it, but tiled windows
- // are coplanar and adjacent by construction, with nothing behind
- // them to separate from. `SHADOW_SIZE` pixels of shadow with only
+ // No shadow for a window that is *actually being tiled right now*
+ // - a real, reported bug, not a style choice made up front: a
+ // drop shadow exists to separate a window from whatever is
+ // visually *behind* it, but a tiled window is coplanar and
+ // adjacent to its neighbours by construction, with nothing behind
+ // it to separate from. `SHADOW_SIZE` pixels of shadow with only
// `gap_inner` pixels of real gap to fall into (as little as 1px)
// has nowhere to land except on the neighbouring tile, darkening
// it by up to `SHADOW_MAX_ALPHA` - reported live as "some windows
- // are dark tinted." Floating windows keep their shadow: they
- // genuinely do sit above other windows, which is exactly where a
- // shadow does its job, and it's also the one case `visible_border_
- // fragments`' own occluder clipping already handles correctly.
+ // are dark tinted."
+ //
+ // Gating this on `w.floating` alone (the first version of this
+ // fix) was a real regression, caught live: `arrange_workspace`
+ // only reads `floating` under the `"tiling"` layout, so every
+ // window on this project's own default `"dynamic"` layout starts
+ // - and stays - `floating: false` unless something explicitly
+ // flips it. That first version read `floating: false` as "this
+ // window is tiled, no shadow" regardless of which layout was
+ // actually running, so *every* window under dynamic/floating mode
+ // silently lost its shadow, recoverable only by toggling `Super+S`
+ // (`srd.window.toggle_floating()`) - which then looked like that
+ // key toggles a "tint", not floating, since floating itself does
+ // nothing visible under a layout that never tiles anyone. `Window::
+ // floating` only ever means "opted out of tiling" *within* a
+ // workspace that tiles at all - checking the workspace's own
+ // layout name first is what this needed instead: a window is only
+ // "currently tiled" when both are true. `currently_tiled` itself
+ // was already computed above, alongside the signature.
let shadows_enabled = self.wm.borrow().shadows_enabled;
- if shadows_enabled && w.floating && !w.maximized && !w.fullscreen {
+ if shadows_enabled && !currently_tiled && !w.maximized && !w.fullscreen {
// A decorated window's corners are *always* rounded (the
// titlebar/border strips round to `corner_radius` regardless of
// this setting - see their own call sites); an undecorated
diff --git a/crates/wayland/src/state/menu.rs b/crates/wayland/src/state/menu.rs
index b0b0a6e..82e4434 100644
--- a/crates/wayland/src/state/menu.rs
+++ b/crates/wayland/src/state/menu.rs
@@ -7,12 +7,28 @@ impl CompState {
/// and caches the rasterised buffer once here rather than per frame --
/// same reasoning as `redraw_decoration_buffer`.
pub(crate) fn open_context_menu(&mut self, window: WindowId, pos: (i32, i32)) {
- let Some(menu) = ({
+ let Some(mut menu) = ({
let wm = self.wm.borrow();
crate::context_menu::ContextMenu::open(&wm, window, pos)
}) else {
return;
};
+ // `ContextMenu::width` is a backend-agnostic placeholder - `core`
+ // has no font of its own to measure real text against, so it can
+ // only ever pick a fixed guess. Widened here to whatever this
+ // menu's own widest real label actually needs, or reported live
+ // as "text goes out of view": the old fixed width comfortably fit
+ // every label back when this menu only listed short ones
+ // ("Minimize", "Close"), but a longer one added since ("Button
+ // Style: Traffic Lights", or a user-configurable workspace name)
+ // just ran past the panel's own right edge, silently cut off
+ // mid-character by `render_context_menu`'s own overflow guard.
+ // Only ever grows the width, never shrinks it below the built-in
+ // minimum `ContextMenu::open` already picked.
+ let font = decoration::find_system_font();
+ let widest_label = menu.items.iter().map(|&(label, _)| decoration::measure_text_width(&font, label, decoration::FONT_PIXELS)).fold(0.0_f32, f32::max);
+ let content_width = (widest_label + decoration::TEXT_LEFT_PADDING * 2.0).ceil() as u32;
+ menu.width = menu.width.max(content_width);
let theme = self.wm.borrow().theme;
let rows: Vec<(&str, bool, u32, decoration::MenuRowKind)> = menu
.items
diff --git a/crates/wayland/src/state/mod.rs b/crates/wayland/src/state/mod.rs
index e07dc1d..6396692 100644
--- a/crates/wayland/src/state/mod.rs
+++ b/crates/wayland/src/state/mod.rs
@@ -114,15 +114,18 @@ pub(crate) struct DecorationSignature {
pub(crate) maximized: bool,
pub(crate) fullscreen: bool,
pub(crate) shadows_enabled: bool,
- /// A tiled window gets no shadow at all (see `redraw_decoration_
- /// buffer`'s own gate) - included here for the same "one signature,
- /// every real input" reason as `maximized`/`fullscreen` just above:
- /// toggling floating on its own (`srd.window.toggle_floating()`,
- /// switching layouts) changes nothing else this signature already
- /// tracks, so without this the cache would keep serving whichever
- /// shadow state happened to be cached until some unrelated field
- /// (a resize, a focus change) forced a rebuild anyway.
- pub(crate) floating: bool,
+ /// A window actually occupying a tiled slot right now (its workspace
+ /// is running the `"tiling"` layout, and it hasn't opted out via
+ /// `Window::floating`) gets no shadow at all - see `redraw_
+ /// decoration_buffer`'s own gate. Deliberately *not* just `Window::
+ /// floating` on its own: a workspace's layout name changing (`Super+
+ /// Shift+t`/`s`) affects every window on it without touching any of
+ /// their own `floating` fields, and toggling `floating` alone
+ /// (`srd.window.toggle_floating()`) changes nothing else this
+ /// signature already tracks - without this field, either change
+ /// would leave the cache serving whichever shadow state it last
+ /// computed until some unrelated field forced a rebuild anyway.
+ pub(crate) currently_tiled: bool,
/// Which of *this* window's own titlebar buttons (if any) is currently
/// hovered, and the glyph-reveal animation's current progress (0..=255)
/// - see `CompState::hovered_titlebar_button`'s own doc comment.
diff --git a/docs/TODO.md b/docs/TODO.md
index 691e63b..68b8486 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -1,5 +1,23 @@
# TODO / planned features - master checklist
+## A real regression in the shadow-tint fix: dynamic-mode windows lost their shadow entirely (2026-08-28)
+
+Reported live: "why is super+s toggling tint/shadow of window. it should toggle tiling/floating." Caught immediately, not defended: the tiled-shadow-tint fix earlier today (`b7f3eff`) gated the shadow on `Window::floating` alone - `if shadows_enabled && w.floating && !w.maximized && !w.fullscreen`. `arrange_workspace` only ever reads `floating` under the `"tiling"` layout; every window on this project's own default `"dynamic"` layout starts, and stays, `floating: false` unless something explicitly flips it. That gate therefore read `floating: false` as "this window is tiled, no shadow" regardless of which layout was actually running - so every window under dynamic/floating mode (this session's own stated daily-driver preference) silently lost its shadow outright, recoverable only by toggling `Super+S` (`srd.window.toggle_floating()`), which then looked like that key toggles a "tint" rather than floating - floating itself does nothing visible under a layout that never tiles anyone, so the shadow reappearing was the *only* thing Super+S visibly did.
+
+Fixed by checking the window's own workspace layout first: `currently_tiled = workspace.layout == "tiling" && !w.floating`, shadow shown whenever `!currently_tiled`. `Window::floating` only ever means "opted out of tiling" *within* a workspace that tiles at all; a dynamic-workspace window is never "tiled" in the first place; regardless of its own `floating` flag, so it now always keeps its shadow, matching every other floating desktop window on any real OS. `DecorationSignature`'s own `floating: bool` field (added alongside the original fix, to invalidate the decoration cache when floating changes) is now `currently_tiled: bool` instead, computed from the workspace's layout too - a `Super+Shift+t`/`s` layout switch changes this for every window on that workspace without touching any of their own `floating` fields, and the old field would have kept serving a stale cached shadow state across exactly that switch.
+
+Separately and directly: "i never ever asked you to tint windows ever and that has caused me a lot of problems in trying to get color accuracy... shadowed borders like how other systems do yes. tinting no." `general.shadows` was never actually asked for - it defaults `true` in the engine itself and had been on the whole time with no line in the user's own `~/.config/srd/init.lua` to show it, confirmed by grep. Set to `false` there now, with the reasoning written into the config itself: a drop shadow is mechanically a translucent dark gradient blended over whatever's behind the window, unavoidably colour-affecting near its own edge regardless of how correctly it renders otherwise - exactly wrong for colour-accuracy work. The window's own solid `border_color`/`border_width` is untouched by this setting and unaffected by any of today's shadow work, still giving every window a fully opaque, crisp edge.
+
+Full workspace build/test/clippy clean.
+
+## Context menu text was silently cut off past a fixed 170px width (2026-08-28)
+
+Reported live: "some of the text goes out of view in the context menu" - clarifying an earlier misdiagnosis (the previous entry below wrongly read this complaint as being about the "Floating" toggle's own inapplicability, which was real but not what was meant). Root cause: both `ContextMenu` (titlebar) and `DesktopMenu` (desktop icons) used a fixed `MENU_WIDTH = 170` regardless of their own actual longest label - comfortably fit short ones ("Minimize", "Close") but not longer ones added since ("Button Style: Traffic Lights", "Open in File Manager", a user-configurable workspace name), which `render_context_menu`'s own overflow guard (`if pen_x as usize >= width { break; }`) just silently truncated mid-character with no ellipsis or indication anything was cut.
+
+`srdwm_core::context_menu` has no font of its own to measure real glyph widths against (it's backend-agnostic by design), so the fix lives on the Wayland side, where one already exists: a new `decoration::measure_text_width` (real per-glyph advance-width summation, the same measurement `render_header_box`'s `draw_centered` already did inline for the lock screen) lets `open_context_menu`/`build_desktop_menu_buffer` widen `menu.width` to whichever real label is actually widest, before rendering - only ever grows the width past the built-in minimum, never shrinks it. Applied to both menus, since both had the identical fixed-width bug, not just the titlebar one that was reported.
+
+Full workspace build/test/clippy clean.
+
## Titlebar right-click menu redesigned: real separators/headers, an inapplicable item hidden, live customization added (2026-08-28)
Reported live: "looks very ugly currently and some of it doesn't make sense." Both were real, found by reading `srdwm_core::context_menu` and `decoration::render_context_menu` directly rather than guessing at what "ugly" meant.