git.lucas.co / cce-window-manager
window management library
git clone https://git.lucas.co/cce-window-manager.git

commit6b83b0ff563888d79f4761ce7bdb15629503e63e
parent3930e591e6
authorLucas Galante <[email protected]>
date2026-09-04 10:35
fix(arrange): round virtual-to-screen placement instead of truncating

virtual_to_screen truncated toward zero while the background grid rounds
its lattice position, a systematic half-pixel disagreement that flipped
sign across the origin: the lattice slid a pixel against window edges as
the camera moved, and the client grid patch (placed through the same
function) landed a pixel off the fallback lattice at every swap. Both
now round to the nearest pixel.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

 src/arrange.rs | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/arrange.rs b/src/arrange.rs
index 33cd147..3312b1d 100644
--- a/src/arrange.rs
+++ b/src/arrange.rs
@@ -224,10 +224,17 @@ pub struct PlacementCtx {
 }
 
 impl PlacementCtx {
+    /// Rounds to the nearest pixel, the same way the background grid places
+    /// its lattice (`background::grid_frame`). It used to truncate toward
+    /// zero: a systematic half-pixel disagreement with the grid, flipping
+    /// sign across the origin, so the lattice slid a pixel against window
+    /// edges as the camera moved — and the client grid patch (placed through
+    /// this same function) landed a pixel off the fallback lattice at every
+    /// swap.
     fn virtual_to_screen(&self, vx: f64, vy: f64) -> (i32, i32) {
         (
-            self.phys.x + ((vx - self.pan_x) * self.zoom) as i32,
-            self.phys.y + ((vy - self.pan_y) * self.zoom) as i32,
+            self.phys.x + ((vx - self.pan_x) * self.zoom).round() as i32,
+            self.phys.y + ((vy - self.pan_y) * self.zoom).round() as i32,
         )
     }