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:
@@ -48,6 +48,11 @@ class PadCanvasView(context: Context) : View(context) {
|
|||||||
private var gridCanvas: Canvas? = null
|
private var gridCanvas: Canvas? = null
|
||||||
private var gridDirty = true
|
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 ---
|
// --- In-progress stroke ---
|
||||||
private var currentPath: Path? = null
|
private var currentPath: Path? = null
|
||||||
private var currentPaint: Paint? = null
|
private var currentPaint: Paint? = null
|
||||||
@@ -269,11 +274,24 @@ class PadCanvasView(context: Context) : View(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
// Fill entire view with white (no super.onDraw to avoid double-clear flicker)
|
val w = width
|
||||||
canvas.drawColor(Color.WHITE)
|
val h = height
|
||||||
|
if (w <= 0 || h <= 0) return
|
||||||
|
|
||||||
// Draw page background in canonical space
|
// Ensure compositing bitmap exists at view size
|
||||||
canvas.withMatrix(viewMatrix) {
|
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(
|
drawRect(
|
||||||
0f, 0f,
|
0f, 0f,
|
||||||
canvasState.pageSize.widthPt.toFloat(),
|
canvasState.pageSize.widthPt.toFloat(),
|
||||||
@@ -282,24 +300,22 @@ class PadCanvasView(context: Context) : View(context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw cached grid
|
// Cached grid
|
||||||
ensureGrid()
|
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()
|
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)
|
// Dynamic elements
|
||||||
canvas.withMatrix(viewMatrix) {
|
c.withMatrix(viewMatrix) {
|
||||||
// In-progress stroke
|
|
||||||
currentPath?.let { path ->
|
currentPath?.let { path ->
|
||||||
currentPaint?.let { paint ->
|
currentPaint?.let { paint ->
|
||||||
drawPath(path, paint)
|
drawPath(path, paint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Box preview
|
|
||||||
if (isDrawingBox) {
|
if (isDrawingBox) {
|
||||||
boxPreviewPaint.strokeWidth = canvasState.penWidthPt
|
boxPreviewPaint.strokeWidth = canvasState.penWidthPt
|
||||||
val left = minOf(boxStartX, boxEndX)
|
val left = minOf(boxStartX, boxEndX)
|
||||||
@@ -309,7 +325,6 @@ class PadCanvasView(context: Context) : View(context) {
|
|||||||
drawRect(left, top, right, bottom, boxPreviewPaint)
|
drawRect(left, top, right, bottom, boxPreviewPaint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Line preview
|
|
||||||
if (isDrawingLine) {
|
if (isDrawingLine) {
|
||||||
val paint = if (canvasState.lineStyle == LineStyle.DASHED) {
|
val paint = if (canvasState.lineStyle == LineStyle.DASHED) {
|
||||||
dashedLinePaint.also { it.strokeWidth = canvasState.penWidthPt }
|
dashedLinePaint.also { it.strokeWidth = canvasState.penWidthPt }
|
||||||
@@ -323,7 +338,6 @@ class PadCanvasView(context: Context) : View(context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selection highlights
|
|
||||||
if (selectedStrokeIds.isNotEmpty()) {
|
if (selectedStrokeIds.isNotEmpty()) {
|
||||||
for (sr in completedStrokes) {
|
for (sr in completedStrokes) {
|
||||||
if (sr.id in selectedStrokeIds) {
|
if (sr.id in selectedStrokeIds) {
|
||||||
@@ -334,7 +348,6 @@ class PadCanvasView(context: Context) : View(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selection rectangle
|
|
||||||
if (isSelecting) {
|
if (isSelecting) {
|
||||||
val left = minOf(selectionStartX, selectionEndX)
|
val left = minOf(selectionStartX, selectionEndX)
|
||||||
val top = minOf(selectionStartY, selectionEndY)
|
val top = minOf(selectionStartY, selectionEndY)
|
||||||
@@ -343,6 +356,9 @@ class PadCanvasView(context: Context) : View(context) {
|
|||||||
drawRect(left, top, right, bottom, selectionRectPaint)
|
drawRect(left, top, right, bottom, selectionRectPaint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Present the fully composed frame in one operation
|
||||||
|
canvas.drawBitmap(compositeBitmap!!, 0f, 0f, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -243,7 +243,9 @@ fun NotebookListScreen(
|
|||||||
CreateNotebookDialog(
|
CreateNotebookDialog(
|
||||||
onDismiss = { showCreateDialog = false },
|
onDismiss = { showCreateDialog = false },
|
||||||
onCreate = { title, pageSize ->
|
onCreate = { title, pageSize ->
|
||||||
viewModel.createNotebook(title, pageSize)
|
viewModel.createNotebook(title, pageSize) { notebookId ->
|
||||||
|
onNotebookClick(notebookId)
|
||||||
|
}
|
||||||
showCreateDialog = false
|
showCreateDialog = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ class NotebookListViewModel(
|
|||||||
val notebooks: StateFlow<List<Notebook>> = repository.getAll()
|
val notebooks: StateFlow<List<Notebook>> = repository.getAll()
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||||
|
|
||||||
fun createNotebook(title: String, pageSize: String) {
|
fun createNotebook(title: String, pageSize: String, onCreated: (Long) -> Unit = {}) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
repository.create(title, pageSize)
|
val id = repository.create(title, pageSize)
|
||||||
|
onCreated(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user