From 2b567aa038ba1d1ab327a64ca6fa8cc178c48e7a Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Tue, 24 Mar 2026 15:09:57 -0700 Subject: [PATCH] Enforce minimum zoom so page always fills viewport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dynamic min zoom computed from page and view dimensions — page can never be smaller than the visible area. Combined with pan clamping from previous commit, the page always covers the full canvas with no non-drawable area visible. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../engpad/ui/editor/PadCanvasView.kt | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/net/metacircular/engpad/ui/editor/PadCanvasView.kt b/app/src/main/kotlin/net/metacircular/engpad/ui/editor/PadCanvasView.kt index d80d638..fc10016 100644 --- a/app/src/main/kotlin/net/metacircular/engpad/ui/editor/PadCanvasView.kt +++ b/app/src/main/kotlin/net/metacircular/engpad/ui/editor/PadCanvasView.kt @@ -93,7 +93,7 @@ class PadCanvasView(context: Context) : View(context) { object : ScaleGestureDetector.SimpleOnScaleGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { val newZoom = (zoom * detector.scaleFactor) - .coerceIn(CanvasState.MIN_ZOOM, CanvasState.MAX_ZOOM) + .coerceIn(minZoom(), CanvasState.MAX_ZOOM) if (newZoom != zoom) { val focusX = detector.focusX val focusY = detector.focusY @@ -639,6 +639,23 @@ class PadCanvasView(context: Context) : View(context) { // --- Coordinate transforms --- + /** + * Minimum zoom so the page always fills the viewport. + * At fitScale (zoom=1), the page fills the screen width. + * We need both dimensions covered, so min zoom = max(1, viewH/(pageH*fitScale)). + */ + private fun minZoom(): Float { + val pageW = canvasState.pageSize.widthPt.toFloat() + val pageH = canvasState.pageSize.heightPt.toFloat() + val viewW = width.toFloat().coerceAtLeast(1f) + val viewH = height.toFloat().coerceAtLeast(1f) + val fitScale = viewW / pageW + // Page must fill both dimensions + val minForWidth = 1f // zoom=1 means page width = view width + val minForHeight = viewH / (pageH * fitScale) + return maxOf(minForWidth, minForHeight) + } + private fun rebuildViewMatrix() { viewMatrix.reset() val pageW = canvasState.pageSize.widthPt.toFloat()