From 48a43aae11606fe3956319fd75be8b68b597c16d Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 17 Jul 2026 13:44:20 +0300 Subject: [PATCH] fix(dyn): clamp guide length to the viewport so a huge distance can't OOM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dynamic-input guides are dotted, and a dash pattern is measured in pixels: the tessellator emits a quad every few pixels, so a guide costs in proportion to its SCREEN length, not to what it represents. Entering a large distance (5000000 in the value box) projects the cursor millions of pixels from the anchor, so the guide asks for millions of dash quads — gigabytes — and the process fills RAM and dies while building a frame whose content is off-screen anyway (#406). Clamp the drawn length to just past the viewport diagonal. `dx`/`dy` still come from the true cursor, and the angle and read-outs are computed from the raw length, so direction and every displayed value are unchanged — only the tail past the viewport edge, which nobody can see, is dropped. Clamping `cursor` bounds every guide below it: they all derive from it, including the polar arc and the perpendicular endpoint. The reporter's own bisect points here: with DYN off the crash does not happen, and drawing the same line by command (which builds no interactive guide) never reproduced it. Co-Authored-By: Claude Opus 4.8 --- src/ui/overlay.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/ui/overlay.rs b/src/ui/overlay.rs index 43ecd34ec..700bab9eb 100644 --- a/src/ui/overlay.rs +++ b/src/ui/overlay.rs @@ -1757,10 +1757,29 @@ impl DynInputCanvas { /// Guided layout: draw the guide geometry anchored at `base`, then place /// each box according to its role. fn draw_guided(&self, frame: &mut canvas::Frame, bounds: iced::Rectangle, base: Point) { - let cursor = self.cursor_screen; - let (vx, vy) = (cursor.x - base.x, cursor.y - base.y); - let len = (vx * vx + vy * vy).sqrt().max(1.0); - let (dx, dy) = (vx / len, vy / len); + let cursor_raw = self.cursor_screen; + let (vx, vy) = (cursor_raw.x - base.x, cursor_raw.y - base.y); + let raw_len = (vx * vx + vy * vy).sqrt().max(1.0); + let (dx, dy) = (vx / raw_len, vy / raw_len); + // Clamp the drawn length to just past the viewport. + // + // These guides are dotted, and a dash pattern is measured in PIXELS: the + // tessellator emits a quad every few pixels, so the cost scales with a + // guide's screen length, not with what it represents. Typing a large + // distance (5000000 into the value box) projects the cursor millions of + // pixels away — millions of quads, gigabytes of them — and the process + // dies building a frame whose content is off-screen anyway (#406). + // + // Everything past the viewport edge is invisible, so dropping it costs + // nothing: `dx`/`dy` keep the true direction, the angle and every + // read-out are computed from `raw_len` above, and only the tail nobody + // can see is cut. Clamping `cursor` here bounds every guide below — + // they all derive from it. + let len = raw_len.min(bounds.width.hypot(bounds.height) * 1.5); + let cursor = Point { + x: base.x + dx * len, + y: base.y + dy * len, + }; // Perpendicular pointing to the lower half so labels sit under the line. let (mut nx, mut ny) = (-dy, dx); if ny < 0.0 {