desktop grid client
git clone https://git.lucas.co/cce-grid.git
fix: pointer and region math in surface px — the grid surface has no ui scale
Input regions, item hit-tests, drag deltas, and drop positions all divided
by the process-wide output scale (logical_per_virtual). But this surface is
pinned at buffer_scale 1 — cce-ui ignores scale events for grid apps, the
patch is the sole resolution authority — so its surface-local coordinates
are BUFFER px at every output scale, and patch.scale alone is the right
conversion (surface_per_virtual now).
The /ui came from 7e027a5, calibrated against the compositor's old
hit-test, which handed out raw layout offsets instead of surface
coordinates. Those two errors cancelled only at camera zoom 1 on the pow2
patch quantization. Anywhere else the input region sat displaced from the
drawn items — on a scale-2 output at half their offset and size, putting it
entirely off the visible canvas, so every press fell through to the desktop
(and in overview, exited it) — and when a press did land, its position
disagreed with the drag-delta space by the display ratio times the surface
position, which is what kept flinging the dragged image thousands of
virtual units. The compositor speaks true surface coordinates since
cce-compositor@feab593, so the compensation now inverts into exactly these
bugs.
Verified in shadow sessions at output scale 1 and 2: regions land exactly
under the drawn items, presses hit, and drags track the pointer 1:1 in
normal mode, in overview, and across a camera flight held mid-grab.
Co-Authored-By: Claude Fable 5 <[email protected]>
CLAUDE.md | 21 ++++++++++++++-------
src/main.rs | 39 +++++++++++++++++++--------------------
2 files changed, 33 insertions(+), 27 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 9e3a3a2..8edb853 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -72,13 +72,20 @@ popup at `ccectl pointer-location`, whose reply comes back as
that menu blocks on its own thread, and the list can be reordered by a drag or
grown by a drop while it is open.
-One trap, spelled out on `Patch::logical_per_virtual`: `Patch::scale` is
-BUFFER px per virtual unit — it already folds in the output scale, which is
-why the paint path uses it directly — while pointer events and input regions
-are surface-local LOGICAL px. On a scale-2 display the two differ by exactly
-the output scale, which put every input region at twice its size and offset
-(clicks missed the image entirely) and landed every drop at half its intended
-position.
+One trap, spelled out on `Patch::surface_per_virtual`: pointer events and
+input regions are surface-local px, and for THIS surface that means BUFFER
+px at every output scale — the grid surface is pinned at buffer_scale 1
+(cce-ui ignores scale events for grid apps; patch.scale is the sole
+resolution authority), so `Patch::scale` is the one conversion for paint,
+regions, and pointer math alike. This replaced a `/ui` division that had
+been calibrated against the compositor's old hit-test, which handed out raw
+layout offsets: numerically buffer/ui only at camera zoom 1 on the pow2
+patch quantization, and at any other camera state it displaced the input
+region off the items (presses read as background — in overview they EXITED
+it) and tore the press position apart from the drag deltas, flinging the
+grabbed item thousands of virtual units. The compositor's hit-test speaks
+true surface coordinates since cce-compositor@feab593; do not reintroduce
+output-scale terms here.
This directory is its own git repository (gitsite-published, fetch-only
origin; committing locally is publishing). `cce-grid.service` autostarts it
diff --git a/src/main.rs b/src/main.rs
index 4dcc9c8..0b18e54 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -70,22 +70,21 @@ struct GridApp {
}
impl Patch {
- /// Surface-local logical px per virtual unit.
+ /// Surface-local px per virtual unit — `Patch::scale` itself.
///
- /// `Patch::scale` is BUFFER px per virtual unit — it already folds in the
- /// output scale, which is why the paint path uses it directly. Pointer
- /// events and input regions are not in that space: both are surface-local
- /// LOGICAL px. On a scale-2 display the two differ by exactly the output
- /// scale, which put every input region at twice its size and offset (so
- /// clicks missed the image entirely) and made every drop land at half its
- /// distance from the patch origin.
- fn logical_per_virtual(&self) -> f64 {
- let ui = cce_ui::scale::scale_factor() as f64;
- if ui > 0.0 {
- self.scale / ui
- } else {
- self.scale
- }
+ /// The grid surface is PINNED at buffer_scale 1 (cce-ui ignores scale
+ /// events for grid apps; patch.scale is the sole resolution authority),
+ /// so surface-local coordinates ARE buffer px at every output scale:
+ /// pointer events arrive in that space and input regions are interpreted
+ /// in it. The /ui division that used to live here calibrated against the
+ /// compositor's old hit-test, which handed out raw layout offsets —
+ /// numerically buffer/ui only at zoom 1 on the pow2 patch quantization —
+ /// and at any other camera state it displaced the input region off the
+ /// items (presses read as background) and tore the press position apart
+ /// from the drag deltas (the flung-item bug). The compositor now speaks
+ /// true surface coordinates, so the patch scale is used unmodified.
+ fn surface_per_virtual(&self) -> f64 {
+ self.scale
}
}
@@ -500,7 +499,7 @@ impl Application for GridApp {
// The drop point in world coordinates — the inverse of the mapping
// `paint` uses to place cells, so the image lands under the cursor
// whatever the camera is doing.
- let s = patch.logical_per_virtual();
+ let s = patch.surface_per_virtual();
let vx = patch.x + pos.x as f64 / s;
let vy = patch.y + pos.y as f64 / s;
@@ -560,7 +559,7 @@ impl Application for GridApp {
if p.scale <= 0.0 {
return Some(Vec::new());
}
- let s = p.logical_per_virtual();
+ let s = p.surface_per_virtual();
let out: Vec<(i32, i32, i32, i32)> = self
.items
.iter()
@@ -591,7 +590,7 @@ impl Application for GridApp {
drag.last_origin = origin;
return;
}
- let s = p.logical_per_virtual();
+ let s = p.surface_per_virtual();
let dx = (pos.x - last_pos.0) as f64 / s;
let dy = (pos.y - last_pos.1) as f64 / s;
if dx == 0.0 && dy == 0.0 {
@@ -621,7 +620,7 @@ impl Application for GridApp {
if state != ElementState::Pressed {
return None;
}
- let s = p.logical_per_virtual();
+ let s = p.surface_per_virtual();
let vx = p.x + pos.x as f64 / s;
let vy = p.y + pos.y as f64 / s;
let hit = self.items.iter().rposition(|(i, _)| {
@@ -647,7 +646,7 @@ impl Application for GridApp {
}
match state {
ElementState::Pressed => {
- let s = p.logical_per_virtual();
+ let s = p.surface_per_virtual();
let vx = p.x + pos.x as f64 / s;
let vy = p.y + pos.y as f64 / s;
// Last drawn is on top, so search backwards and take the