From 7a94704c8c9e0ba4d1e9fe41b2f2a95ea3f84697 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Tue, 24 Mar 2026 18:06:25 -0700 Subject: [PATCH] Fix CopyStrokesAction ID tracking and cache grid bitmap - CopyStrokesAction: use insertAll return value (List) to get inserted IDs directly instead of fragile takeLast(n) re-fetch - StrokeDao.insertAll now returns List - Grid bitmap cached separately from stroke bitmap, redrawn only on zoom/pan/resize changes. Eliminates per-frame grid line rendering during active drawing. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../metacircular/engpad/data/db/StrokeDao.kt | 2 +- .../engpad/data/repository/PageRepository.kt | 2 +- .../engpad/ui/editor/PadCanvasView.kt | 31 +++++++++++++++++-- .../engpad/undo/SelectionActions.kt | 10 +++--- 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/net/metacircular/engpad/data/db/StrokeDao.kt b/app/src/main/kotlin/net/metacircular/engpad/data/db/StrokeDao.kt index dd2ed68..e342bde 100644 --- a/app/src/main/kotlin/net/metacircular/engpad/data/db/StrokeDao.kt +++ b/app/src/main/kotlin/net/metacircular/engpad/data/db/StrokeDao.kt @@ -11,7 +11,7 @@ interface StrokeDao { suspend fun insert(stroke: Stroke): Long @Insert - suspend fun insertAll(strokes: List) + suspend fun insertAll(strokes: List): List @Query("SELECT * FROM strokes WHERE page_id = :pageId ORDER BY stroke_order ASC") suspend fun getByPageId(pageId: Long): List diff --git a/app/src/main/kotlin/net/metacircular/engpad/data/repository/PageRepository.kt b/app/src/main/kotlin/net/metacircular/engpad/data/repository/PageRepository.kt index 8434306..b526b1e 100644 --- a/app/src/main/kotlin/net/metacircular/engpad/data/repository/PageRepository.kt +++ b/app/src/main/kotlin/net/metacircular/engpad/data/repository/PageRepository.kt @@ -49,7 +49,7 @@ class PageRepository( suspend fun deleteStrokes(ids: List) = strokeDao.deleteByIds(ids) - suspend fun insertStrokes(strokes: List) = strokeDao.insertAll(strokes) + suspend fun insertStrokes(strokes: List): List = strokeDao.insertAll(strokes) suspend fun getNextStrokeOrder(pageId: Long): Int = (strokeDao.getMaxStrokeOrder(pageId) ?: 0) + 1 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 e9d2568..e8e1702 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 @@ -43,6 +43,11 @@ class PadCanvasView(context: Context) : View(context) { private var backingCanvas: Canvas? = null private var bitmapDirty = true + // --- Cached grid bitmap (redrawn only on zoom/pan/resize) --- + private var gridBitmap: android.graphics.Bitmap? = null + private var gridCanvas: Canvas? = null + private var gridDirty = true + // --- In-progress stroke --- private var currentPath: Path? = null private var currentPaint: Paint? = null @@ -257,6 +262,7 @@ class PadCanvasView(context: Context) : View(context) { super.onSizeChanged(w, h, oldw, oldh) rebuildViewMatrix() bitmapDirty = true + gridDirty = true } override fun onDraw(canvas: Canvas) { @@ -273,8 +279,9 @@ class PadCanvasView(context: Context) : View(context) { ) } - // Draw grid in screen space with pixel-snapped positions - drawGridScreenSpace(canvas) + // Draw cached grid + ensureGrid() + gridBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) } // Draw completed strokes from backing bitmap (screen resolution) ensureBacking() @@ -376,6 +383,25 @@ class PadCanvasView(context: Context) : View(context) { } } + private fun ensureGrid() { + val w = width + val h = height + if (w <= 0 || h <= 0) return + + if (gridBitmap == null || gridBitmap!!.width != w || gridBitmap!!.height != h) { + gridBitmap?.recycle() + gridBitmap = androidx.core.graphics.createBitmap(w, h) + gridCanvas = Canvas(gridBitmap!!) + gridDirty = true + } + + if (gridDirty) { + gridBitmap!!.eraseColor(android.graphics.Color.TRANSPARENT) + drawGridScreenSpace(gridCanvas!!) + gridDirty = false + } + } + private fun drawGridScreenSpace(canvas: Canvas) { val pageW = canvasState.pageSize.widthPt.toFloat() val pageH = canvasState.pageSize.heightPt.toFloat() @@ -1024,6 +1050,7 @@ class PadCanvasView(context: Context) : View(context) { viewMatrix.invert(inverseMatrix) bitmapDirty = true + gridDirty = true } private fun screenToCanonical(screenX: Float, screenY: Float): FloatArray { diff --git a/app/src/main/kotlin/net/metacircular/engpad/undo/SelectionActions.kt b/app/src/main/kotlin/net/metacircular/engpad/undo/SelectionActions.kt index 724bd77..a5c6985 100644 --- a/app/src/main/kotlin/net/metacircular/engpad/undo/SelectionActions.kt +++ b/app/src/main/kotlin/net/metacircular/engpad/undo/SelectionActions.kt @@ -86,12 +86,10 @@ class CopyStrokesAction( createdAt = System.currentTimeMillis(), ) } - repository.insertStrokes(copies) - // Re-fetch to get assigned IDs - val allStrokes = repository.getStrokes(pageId) - val newIds = allStrokes.takeLast(copies.size) - insertedIds.addAll(newIds.map { it.id }) - onExecute(newIds) + val ids = repository.insertStrokes(copies) + insertedIds.addAll(ids) + val inserted = copies.zip(ids) { stroke, id -> stroke.copy(id = id) } + onExecute(inserted) } override suspend fun undo() {