diff options
| author | srdusr <[email protected]> | 2026-07-26 23:02:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2026-07-26 23:02:00 +0200 |
| commit | 492aa5b554ddc389bb2f233c60ea503b3dbda990 (patch) | |
| tree | f483a24188664287237127a1c04ad2f5ff2af414 | |
| parent | 8ed79c21a53e10c29ea35b9a605250ea18f191cf (diff) | |
| download | srdwm-492aa5b554ddc389bb2f233c60ea503b3dbda990.tar.gz srdwm-492aa5b554ddc389bb2f233c60ea503b3dbda990.zip | |
Draw a window's content from the same rect as the frame around it
Reported live: "resizing shrinks/grows only the right side", and "the
titlebar seems separate when resizing, it doesn't size at the same time".
Both are one bug. Every decoration - border strips, titlebar, shadow --
is drawn from `frame`: the client's last committed size, anchored to
whichever edge the drag is not holding. The content was drawn from `geom`:
this compositor's live drag target, which moves on the same frame the
pointer does. A client is always at least one commit behind a drag, so for
that whole interval the two rects disagree, and the window is drawn as two
pieces that move independently - the stale buffer sliding left with the
pointer, carrying its old width, while the border it belongs in stays where
the committed size puts it. Dragging a left edge therefore looked like it
moved the right one.
All three content paths now position from `frame` (both udev render loops
and the winit one). Outside a resize this changes nothing at all:
`committed_frame` only ever corrects the far edge, so `frame.x`/`frame.y`
and `geom.x`/`geom.y` are the same value.
Measured in a nested compositor, driving a real left-edge drag with the
virtual-pointer tool and sampling both the model's target rect and the
rendered pixels at the same moments: the content sits exactly one border
width inside the border on both sides in every frame, the right edge holds
at 830 throughout, and the left edge tracks the pointer (305, then 405).
The earlier decorated-window run measured the same thing for the titlebar:
its left edge moved with the window, its right edge did not move at all.
| -rw-r--r-- | crates/wayland/src/udev/render.rs | 26 | ||||
| -rw-r--r-- | crates/wayland/src/winit/render.rs | 7 |
2 files changed, 30 insertions, 3 deletions
diff --git a/crates/wayland/src/udev/render.rs b/crates/wayland/src/udev/render.rs index c2899ae..a2cf367 100644 --- a/crates/wayland/src/udev/render.rs +++ b/crates/wayland/src/udev/render.rs @@ -315,10 +315,17 @@ impl CompState { let Some(dwindow) = self.id_to_window.get(&id) else { continue }; let Some(surface) = crate::elements::window_wl_surface(dwindow) else { continue }; let geom = self.window_anims.get(&id).map(crate::state::WindowAnim::current_rect).unwrap_or(w.geometry); + // Content is positioned from `frame`, not `geom`, for + // the same reason as the Pixman loop's own content + // push below: `geom` is the live drag target and + // moves a frame ahead of the client, so content drawn + // from it slides out from under the decoration drawn + // around it. + let frame = crate::state::CompState::effective_frame_of(&self.wm, &self.id_to_window, &self.pending_size_configure, id, geom); let band = if w.decorated { srdwm_core::TITLEBAR_HEIGHT as i32 } else { 0 }; let raw_offset = dwindow.geometry().loc; let content_offset = Point::<i32, Logical>::from((raw_offset.x.max(0), raw_offset.y.max(0))); - let pos = (geom.x - origin.x - content_offset.x, geom.y + band - origin.y - content_offset.y); + let pos = (frame.x - origin.x - content_offset.x, frame.y + band - origin.y - content_offset.y); elements.extend(crate::elements::surface_content_elements(&mut gpu.renderer, &surface, pos, w.opacity)); // Border strips (top/bottom) and the titlebar bitmap, // reusing the exact same cached `MemoryRenderBuffer`s @@ -1005,7 +1012,22 @@ impl CompState { // exactly on this same real content top-left -- // the margin has already been compensated for // once, inside the buffer itself. - let content_pos = (geom.x - origin.x, geom.y + band - origin.y); + // `frame`, not `geom`: `geom` is the live drag + // target, which moves with the pointer on the + // very frame the pointer moves, while every + // decoration around it is drawn from `frame` -- + // the committed size anchored to the edge the + // drag is not holding. Drawing content from one + // and its frame from the other is what made a + // titlebar look detached from its own window + // while resizing, and made dragging a left edge + // look like it moved the right one: the stale + // buffer slid left with the pointer, carrying + // its old width, while the border stayed put. + // Outside a resize the two are identical -- + // `committed_frame` only ever corrects the far + // edge - so this changes nothing else. + let content_pos = (frame.x - origin.x, frame.y + band - origin.y); // Only for the *fallback* path below // (`surface_content_elements`, pushed when // masking is off or fails): that renders the diff --git a/crates/wayland/src/winit/render.rs b/crates/wayland/src/winit/render.rs index d49351f..02ee322 100644 --- a/crates/wayland/src/winit/render.rs +++ b/crates/wayland/src/winit/render.rs @@ -489,7 +489,12 @@ impl WaylandPlatform { // shift content away from the border instead of into it. let raw_offset = dwindow.geometry().loc; let content_offset = smithay::utils::Point::<i32, smithay::utils::Logical>::from((raw_offset.x.max(0), raw_offset.y.max(0))); - let pos = (geom.x - content_offset.x, geom.y + band - content_offset.y); + // `frame`, not `geom` - see the matching comment in + // `udev/render.rs`'s own content push: content and the + // decoration around it have to come from the same rect, + // or they visibly disagree for as long as a resize drag + // is ahead of the client. + let pos = (frame.x - content_offset.x, frame.y + band - content_offset.y); let rounded = rounded_corners_enabled.then_some(self.state.rounded_corners_program.as_ref()).flatten().and_then(|program| { let corners = if w.decorated { crate::rounded_corners::RoundedCorners::BOTTOM_ONLY } else { crate::rounded_corners::RoundedCorners::ALL }; crate::rounded_corners::rounded_content_element(renderer, program, &surface, pos, w.opacity, w.corner_radius as f32, corners) |