-
-
Notifications
You must be signed in to change notification settings - Fork 22
code clean up - DrawCanvas #208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
All observer logic from `DrawCanvas.kt` has been extracted into a new `CanvasObserverRegistry.kt` class. This improves separation of concerns by dedicating a class to manage the various event bus and state flow observers. The `DrawCanvas` now initializes and registers these observers through the new registry, cleaning up its own implementation. The visibility of several helper methods in `DrawCanvas` has been changed from `private` to `fun` to allow access from the new registry class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors the editor canvas signaling/observer plumbing by extracting DrawCanvas’s former companion-object event flows into a dedicated CanvasEventBus, and moving the observer wiring into a standalone registry. This reduces the responsibilities of DrawCanvas and updates the rest of the app to use the new event bus.
Changes:
- Introduces
CanvasEventBusto host shared flows/state previously onDrawCanvas.Companion. - Extracts
DrawCanvas.registerObservers()implementation intoCanvasObserverRegistry. - Updates multiple UI/editor components to emit/collect via
CanvasEventBus(including page change events formerly onEditorControlTower).
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| app/src/main/java/com/ethran/notable/ui/dialogs/BackgroundSelector.kt | Switches refresh signaling from DrawCanvas to CanvasEventBus. |
| app/src/main/java/com/ethran/notable/ui/components/QuickNav.kt | Uses CanvasEventBus for save/preview/restore and page-change signaling. |
| app/src/main/java/com/ethran/notable/ui/Router.kt | Uses CanvasEventBus.isDrawing to toggle drawing state around QuickNav. |
| app/src/main/java/com/ethran/notable/editor/utils/Select.kt | Uses CanvasEventBus.refreshUi after selection updates. |
| app/src/main/java/com/ethran/notable/editor/ui/toolbar/ToolbarMenu.kt | Routes clear-page action through CanvasEventBus.clearPageSignal. |
| app/src/main/java/com/ethran/notable/editor/ui/toolbar/Toolbar.kt | Routes image insert + refresh through CanvasEventBus. |
| app/src/main/java/com/ethran/notable/editor/ui/EditorGestureReceiver.kt | Emits gesture selection/force update via CanvasEventBus. |
| app/src/main/java/com/ethran/notable/editor/state/history.kt | Moves history commit coordination to CanvasEventBus flows/deferred. |
| app/src/main/java/com/ethran/notable/editor/drawing/pageDrawing.kt | Clears inserted-image state via CanvasEventBus.addImageByUri. |
| app/src/main/java/com/ethran/notable/editor/PageView.kt | Triggers redraws via CanvasEventBus instead of DrawCanvas flows. |
| app/src/main/java/com/ethran/notable/editor/EditorControlTower.kt | Collects page-change events from CanvasEventBus.changePage. |
| app/src/main/java/com/ethran/notable/editor/DrawCanvas.kt | Removes companion event bus + inlines observer hookup via new registry. |
| app/src/main/java/com/ethran/notable/editor/CanvasObserverRegistry.kt | New: encapsulates all canvas-related flow observers/handlers. |
| app/src/main/java/com/ethran/notable/editor/CanvasEventBus.kt | New: centralized shared flows/state for canvas/editor events. |
| app/src/main/java/com/ethran/notable/data/PageDataManager.kt | Emits canvas redraw on background-file invalidation via CanvasEventBus. |
| app/src/main/java/com/ethran/notable/MainActivity.kt | Uses CanvasEventBus for restart/focus/refresh signaling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
app/src/main/java/com/ethran/notable/editor/drawing/pageDrawing.kt
Outdated
Show resolved
Hide resolved
| private val observers = CanvasObserverRegistry( | ||
| coroutineScope, this, page, state, history, touchHelper | ||
| ) |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CanvasObserverRegistry is constructed with touchHelper, which forces evaluation of the touchHelper lazy during DrawCanvas construction. That changes the lifecycle/initialization timing of TouchHelper.create(...) (it now happens before init()/surface callbacks). Consider making observers lazy as well, or passing a getter (or TouchHelper? provider) so touchHelper stays lazily initialized when first needed.
Moved the `selectRectangle` function from `DrawCanvas.kt` to `editor/utils/Select.kt` for better code organization. The function's call site in `CanvasObserverRegistry.kt` has been updated to reflect this change.
--AI-- - **Extracted `CanvasRefreshManager`:** UI refresh and canvas drawing logic has been moved from `DrawCanvas` into a new `CanvasRefreshManager` class. This centralizes responsibility for screen updates. - **Updated Method Calls:** `OnyxInputHandler`, `DrawCanvas`, and `CanvasObserverRegistry` now delegate UI refresh calls to the new `CanvasRefreshManager` instance. - **Moved History Committing:** The logic for batching and committing stroke history has been moved from `DrawCanvas` to `CanvasObserverRegistry`.
- All classes directly related to the drawing canvas, such as `DrawCanvas`, `CanvasEventBus`, `CanvasRefreshManager`, and `OnyxInputHandler`, have been moved into a new `com.ethran.notable.editor.canvas` package. - Imports have been updated across the application to reflect the new package structure.
All call sites have been updated to use the new location in `CanvasEventBus`.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
app/src/main/java/com/ethran/notable/editor/canvas/CanvasObserverRegistry.kt
Outdated
Show resolved
Hide resolved
app/src/main/java/com/ethran/notable/editor/canvas/CanvasEventBus.kt
Outdated
Show resolved
Hide resolved
| val zoneToRedraw = dirtyRect ?: Rect(0, 0, page.viewWidth, page.viewHeight) | ||
| var canvas: Canvas? = null | ||
| try { | ||
| // Lock the canvas only for the dirtyRect region | ||
| canvas = drawCanvas.holder.lockCanvas(zoneToRedraw) ?: return | ||
|
|
||
| canvas.drawBitmap(page.windowedBitmap, zoneToRedraw, zoneToRedraw, Paint()) | ||
|
|
||
| if (drawCanvas.getActualState().mode == Mode.Select) { | ||
| // render selection, but only within dirtyRect | ||
| drawCanvas.getActualState().selectionState.firstPageCut?.let { cutPoints -> | ||
| log.i("render cut") | ||
| val path = pointsToPath(cutPoints.map { | ||
| SimplePointF( | ||
| it.x - page.scroll.x, it.y - page.scroll.y | ||
| ) | ||
| }) | ||
| canvas.drawPath(path, selectPaint) | ||
| } | ||
| } | ||
| } catch (e: IllegalStateException) { | ||
| log.w("Surface released during draw", e) | ||
| // ignore — surface is gone | ||
| } finally { | ||
| try { | ||
| if (canvas != null) { | ||
| drawCanvas.holder.unlockCanvasAndPost(canvas) | ||
| } | ||
| } catch (e: IllegalStateException) { | ||
| log.w("Surface released during unlock", e) | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| fun restoreCanvas(dirtyRect: Rect, bitmap: Bitmap = page.windowedBitmap) { | ||
| drawCanvas.post { | ||
| val holder = drawCanvas.holder | ||
| var surfaceCanvas: Canvas? = null | ||
| try { | ||
| surfaceCanvas = holder.lockCanvas(dirtyRect) | ||
| // Draw the preview bitmap scaled to fit the dirty rect | ||
| surfaceCanvas.drawBitmap(bitmap, dirtyRect, dirtyRect, null) | ||
| } catch (e: Exception) { | ||
| Log.e("DrawCanvas", "Canvas lock failed: ${e.message}") | ||
| } finally { | ||
| if (surfaceCanvas != null) { | ||
| holder.unlockCanvasAndPost(surfaceCanvas) | ||
| } | ||
| // Trigger partial refresh | ||
| refreshScreenRegion(drawCanvas, dirtyRect) | ||
| } | ||
| } |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CanvasRefreshManager.drawCanvasToView(...) duplicates drawing logic that also exists in DrawCanvas.drawCanvasToView(...) (and similarly for restoreCanvas). Keeping two copies increases the chance they drift. Consider delegating to a single implementation (either have DrawCanvas delegate to CanvasRefreshManager or remove the duplicate methods from the manager).
| val zoneToRedraw = dirtyRect ?: Rect(0, 0, page.viewWidth, page.viewHeight) | |
| var canvas: Canvas? = null | |
| try { | |
| // Lock the canvas only for the dirtyRect region | |
| canvas = drawCanvas.holder.lockCanvas(zoneToRedraw) ?: return | |
| canvas.drawBitmap(page.windowedBitmap, zoneToRedraw, zoneToRedraw, Paint()) | |
| if (drawCanvas.getActualState().mode == Mode.Select) { | |
| // render selection, but only within dirtyRect | |
| drawCanvas.getActualState().selectionState.firstPageCut?.let { cutPoints -> | |
| log.i("render cut") | |
| val path = pointsToPath(cutPoints.map { | |
| SimplePointF( | |
| it.x - page.scroll.x, it.y - page.scroll.y | |
| ) | |
| }) | |
| canvas.drawPath(path, selectPaint) | |
| } | |
| } | |
| } catch (e: IllegalStateException) { | |
| log.w("Surface released during draw", e) | |
| // ignore — surface is gone | |
| } finally { | |
| try { | |
| if (canvas != null) { | |
| drawCanvas.holder.unlockCanvasAndPost(canvas) | |
| } | |
| } catch (e: IllegalStateException) { | |
| log.w("Surface released during unlock", e) | |
| } | |
| } | |
| } | |
| fun restoreCanvas(dirtyRect: Rect, bitmap: Bitmap = page.windowedBitmap) { | |
| drawCanvas.post { | |
| val holder = drawCanvas.holder | |
| var surfaceCanvas: Canvas? = null | |
| try { | |
| surfaceCanvas = holder.lockCanvas(dirtyRect) | |
| // Draw the preview bitmap scaled to fit the dirty rect | |
| surfaceCanvas.drawBitmap(bitmap, dirtyRect, dirtyRect, null) | |
| } catch (e: Exception) { | |
| Log.e("DrawCanvas", "Canvas lock failed: ${e.message}") | |
| } finally { | |
| if (surfaceCanvas != null) { | |
| holder.unlockCanvasAndPost(surfaceCanvas) | |
| } | |
| // Trigger partial refresh | |
| refreshScreenRegion(drawCanvas, dirtyRect) | |
| } | |
| } | |
| // Delegate drawing logic to DrawCanvas to avoid duplication. | |
| drawCanvas.drawCanvasToView(dirtyRect) | |
| } | |
| fun restoreCanvas(dirtyRect: Rect, bitmap: Bitmap = page.windowedBitmap) { | |
| // Delegate restore logic to DrawCanvas to avoid duplication. | |
| drawCanvas.restoreCanvas(dirtyRect, bitmap) |
Extract drawing and erasing logic from `onRawDrawingTouchPointListReceived` into their own dedicated functions, `onRawDrawingList` and `onRawErasingList`, respectively. This improves code organization and readability.
CanvasEventBus - Extract the companion object from DrawCanvas.kt