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) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 21:59:56 -07:00
parent 86a2ba0f6b
commit 73f35cd33d
3 changed files with 37 additions and 18 deletions

View File

@@ -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)
}
/**

View File

@@ -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
},
)

View File

@@ -17,9 +17,10 @@ class NotebookListViewModel(
val notebooks: StateFlow<List<Notebook>> = 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)
}
}