From 73f35cd33d72a7d0a3023d8654b41b6d9b689485 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Tue, 24 Mar 2026 21:59:56 -0700 Subject: [PATCH] Navigate to editor on notebook creation, reduce flicker with double buffering - Creating a notebook now auto-navigates to its first page editor (createNotebook returns ID via callback, triggers onNotebookClick) - Double-buffered rendering: all layers composited to an off-screen bitmap before presenting to the display in a single drawBitmap call. Eliminates visible intermediate states (white flash between layers) that caused flicker on e-ink displays. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../engpad/ui/editor/PadCanvasView.kt | 46 +++++++++++++------ .../engpad/ui/notebooks/NotebookListScreen.kt | 4 +- .../ui/notebooks/NotebookListViewModel.kt | 5 +- 3 files changed, 37 insertions(+), 18 deletions(-) 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 f0c9fb1..fd58d64 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 @@ -48,6 +48,11 @@ class PadCanvasView(context: Context) : View(context) { private var gridCanvas: Canvas? = null private var gridDirty = true + // --- Compositing bitmap (eliminates flicker by drawing all layers + // off-screen before presenting to the display in one operation) --- + private var compositeBitmap: android.graphics.Bitmap? = null + private var compositeCanvas: Canvas? = null + // --- In-progress stroke --- private var currentPath: Path? = null private var currentPaint: Paint? = null @@ -269,11 +274,24 @@ class PadCanvasView(context: Context) : View(context) { } override fun onDraw(canvas: Canvas) { - // Fill entire view with white (no super.onDraw to avoid double-clear flicker) - canvas.drawColor(Color.WHITE) + val w = width + val h = height + if (w <= 0 || h <= 0) return - // Draw page background in canonical space - canvas.withMatrix(viewMatrix) { + // Ensure compositing bitmap exists at view size + if (compositeBitmap == null || compositeBitmap!!.width != w || compositeBitmap!!.height != h) { + compositeBitmap?.recycle() + compositeBitmap = androidx.core.graphics.createBitmap(w, h) + compositeCanvas = Canvas(compositeBitmap!!) + } + + val c = compositeCanvas!! + + // Compose all layers off-screen + c.drawColor(Color.WHITE) + + // Page background + c.withMatrix(viewMatrix) { drawRect( 0f, 0f, canvasState.pageSize.widthPt.toFloat(), @@ -282,24 +300,22 @@ class PadCanvasView(context: Context) : View(context) { ) } - // Draw cached grid + // Cached grid ensureGrid() - gridBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) } + gridBitmap?.let { c.drawBitmap(it, 0f, 0f, null) } - // Draw completed strokes from backing bitmap (screen resolution) + // Completed strokes ensureBacking() - backingBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) } + backingBitmap?.let { c.drawBitmap(it, 0f, 0f, null) } - // Draw dynamic elements in canonical space (in-progress stroke, previews, selection) - canvas.withMatrix(viewMatrix) { - // In-progress stroke + // Dynamic elements + c.withMatrix(viewMatrix) { currentPath?.let { path -> currentPaint?.let { paint -> drawPath(path, paint) } } - // Box preview if (isDrawingBox) { boxPreviewPaint.strokeWidth = canvasState.penWidthPt val left = minOf(boxStartX, boxEndX) @@ -309,7 +325,6 @@ class PadCanvasView(context: Context) : View(context) { drawRect(left, top, right, bottom, boxPreviewPaint) } - // Line preview if (isDrawingLine) { val paint = if (canvasState.lineStyle == LineStyle.DASHED) { dashedLinePaint.also { it.strokeWidth = canvasState.penWidthPt } @@ -323,7 +338,6 @@ class PadCanvasView(context: Context) : View(context) { ) } - // Selection highlights if (selectedStrokeIds.isNotEmpty()) { for (sr in completedStrokes) { if (sr.id in selectedStrokeIds) { @@ -334,7 +348,6 @@ class PadCanvasView(context: Context) : View(context) { } } - // Selection rectangle if (isSelecting) { val left = minOf(selectionStartX, selectionEndX) val top = minOf(selectionStartY, selectionEndY) @@ -343,6 +356,9 @@ class PadCanvasView(context: Context) : View(context) { drawRect(left, top, right, bottom, selectionRectPaint) } } + + // Present the fully composed frame in one operation + canvas.drawBitmap(compositeBitmap!!, 0f, 0f, null) } /** diff --git a/app/src/main/kotlin/net/metacircular/engpad/ui/notebooks/NotebookListScreen.kt b/app/src/main/kotlin/net/metacircular/engpad/ui/notebooks/NotebookListScreen.kt index 902a882..8b525fb 100644 --- a/app/src/main/kotlin/net/metacircular/engpad/ui/notebooks/NotebookListScreen.kt +++ b/app/src/main/kotlin/net/metacircular/engpad/ui/notebooks/NotebookListScreen.kt @@ -243,7 +243,9 @@ fun NotebookListScreen( CreateNotebookDialog( onDismiss = { showCreateDialog = false }, onCreate = { title, pageSize -> - viewModel.createNotebook(title, pageSize) + viewModel.createNotebook(title, pageSize) { notebookId -> + onNotebookClick(notebookId) + } showCreateDialog = false }, ) diff --git a/app/src/main/kotlin/net/metacircular/engpad/ui/notebooks/NotebookListViewModel.kt b/app/src/main/kotlin/net/metacircular/engpad/ui/notebooks/NotebookListViewModel.kt index 764df64..f435197 100644 --- a/app/src/main/kotlin/net/metacircular/engpad/ui/notebooks/NotebookListViewModel.kt +++ b/app/src/main/kotlin/net/metacircular/engpad/ui/notebooks/NotebookListViewModel.kt @@ -17,9 +17,10 @@ class NotebookListViewModel( val notebooks: StateFlow> = repository.getAll() .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList()) - fun createNotebook(title: String, pageSize: String) { + fun createNotebook(title: String, pageSize: String, onCreated: (Long) -> Unit = {}) { viewModelScope.launch { - repository.create(title, pageSize) + val id = repository.create(title, pageSize) + onCreated(id) } }