Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 49 additions & 45 deletions app/src/main/java/com/ethran/notable/editor/DrawCanvas.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ethran.notable.editor

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
Expand All @@ -9,10 +10,10 @@ import android.graphics.Rect
import android.graphics.RectF
import android.net.Uri
import android.os.Looper
import android.view.MotionEvent
import android.view.SurfaceHolder
import android.view.SurfaceView
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.unit.dp
Expand All @@ -21,7 +22,6 @@ import com.ethran.notable.data.AppRepository
import com.ethran.notable.data.PageDataManager
import com.ethran.notable.data.datastore.GlobalAppSettings
import com.ethran.notable.data.db.Image
import com.ethran.notable.data.db.StrokePoint
import com.ethran.notable.data.model.SimplePointF
import com.ethran.notable.editor.drawing.OpenGLRenderer
import com.ethran.notable.editor.drawing.drawImage
Expand All @@ -31,6 +31,7 @@ import com.ethran.notable.editor.state.History
import com.ethran.notable.editor.state.Mode
import com.ethran.notable.editor.state.Operation
import com.ethran.notable.editor.state.PlacementMode
import com.ethran.notable.editor.utils.DeviceCompat
import com.ethran.notable.editor.utils.Eraser
import com.ethran.notable.editor.utils.Pen
import com.ethran.notable.editor.utils.calculateBoundingBox
Expand Down Expand Up @@ -104,7 +105,7 @@ val pressure = EpdController.getMaxTouchPressure()
// keep reference of the surface view presently associated to the singleton touchhelper
var referencedSurfaceView: String = ""

// TODO: Do not recreate surface on every page change
@SuppressLint("ViewConstructor") // we never execute constructor from XML
class DrawCanvas(
context: Context,
val coroutineScope: CoroutineScope,
Expand All @@ -116,22 +117,31 @@ class DrawCanvas(
private val logCanvasObserver = ShipBook.getLogger("CanvasObservers")
private val log = ShipBook.getLogger("DrawCanvas")
var lastStrokeEndTime: Long = 0
//private val commitHistorySignal = MutableSharedFlow<Unit>()

private var glRenderer = OpenGLRenderer(this)
override fun onAttachedToWindow() {
log.d("Attached to window")
glRenderer = OpenGLRenderer(this@DrawCanvas)
super.onAttachedToWindow()
glRenderer.attachSurfaceView(this)
override fun onTouchEvent(event: MotionEvent): Boolean { //Custom view DrawCanvas overrides onTouchEvent but not performClick
if (event.action == MotionEvent.ACTION_UP) {
performClick()
}
// We will only capture stylus events, and past rest down
// log.d("onTouchEvent, ${event.getToolType(0)}")
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS ||
event.getToolType(0) == MotionEvent.TOOL_TYPE_ERASER
) {
if (!DeviceCompat.isOnyxDevice || isErasing)
return glRenderer.onTouchListener.onTouch(this, event)
else return true
}
// Pass everything else down
return super.onTouchEvent(event)
}

override fun onDetachedFromWindow() {
log.d("Detached from window")
glRenderer.release()
super.onDetachedFromWindow()
@Suppress("RedundantOverride")
override fun performClick(): Boolean {
return super.performClick()
}

private var glRenderer = OpenGLRenderer(this)

var isErasing: Boolean = false

companion object {
Expand All @@ -142,7 +152,6 @@ class DrawCanvas(
)
var isDrawing = MutableSharedFlow<Boolean>()
var restartAfterConfChange = MutableSharedFlow<Unit>()
var eraserTouchPoint = MutableSharedFlow<Offset?>() //TODO: replace with proper solution

// used for managing drawing state on regain focus
val onFocusChange = MutableSharedFlow<Boolean>()
Expand Down Expand Up @@ -229,6 +238,7 @@ class DrawCanvas(
}

override fun onRawDrawingTouchPointListReceived(plist: TouchPointList) {
if(touchHelper == null) return
val currentLastStrokeEndTime = lastStrokeEndTime
lastStrokeEndTime = System.currentTimeMillis()
val startTime = System.currentTimeMillis()
Expand Down Expand Up @@ -341,7 +351,7 @@ class DrawCanvas(
partialRefreshRegionOnce(
this@DrawCanvas,
erasedByScribbleDirtyRect,
touchHelper
touchHelper!!
)

}
Expand All @@ -357,8 +367,9 @@ class DrawCanvas(

// Handle button/eraser tip of the pen:
override fun onBeginRawErasing(p0: Boolean, p1: TouchPoint?) {
if(touchHelper == null) return
if (GlobalAppSettings.current.openGLRendering) {
prepareForPartialUpdate(this@DrawCanvas, touchHelper)
prepareForPartialUpdate(this@DrawCanvas, touchHelper!!)
log.d("Eraser Mode")
}
isErasing = true
Expand Down Expand Up @@ -414,16 +425,24 @@ class DrawCanvas(
}

private val touchHelper by lazy {
referencedSurfaceView = this.hashCode().toString()
TouchHelper.create(this, inputCallback)
val helper = if (DeviceCompat.isOnyxDevice) {
try {
referencedSurfaceView = this.hashCode().toString()
TouchHelper.create(this, inputCallback)
} catch (t: Throwable) {
android.util.Log.w("OnyxInputHandler", "TouchHelper.create failed: ${t.message}")
null
}
} else null
helper
}

fun init() {
log.i("Initializing Canvas")
glRenderer.release()
glRenderer = OpenGLRenderer(this@DrawCanvas)
glRenderer.attachSurfaceView(this)

// This does not work, as EditorGestureReceiver is stealing all the events.
setOnTouchListener(glRenderer.onTouchListener)

val surfaceCallback: SurfaceHolder.Callback = object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
Expand Down Expand Up @@ -456,7 +475,7 @@ class DrawCanvas(
)
holder.removeCallback(this)
if (referencedSurfaceView == this@DrawCanvas.hashCode().toString()) {
touchHelper.closeRawDrawing()
touchHelper?.closeRawDrawing()
}
onSurfaceDestroy(this@DrawCanvas, touchHelper)
}
Expand Down Expand Up @@ -564,23 +583,6 @@ class DrawCanvas(
drawCanvasToView(null)
}
}
coroutineScope.launch {
eraserTouchPoint.collect { p ->
if (!isErasing || !GlobalAppSettings.current.openGLRendering) {
return@collect
}
logCanvasObserver.v("collected: $p")
if (p == null) return@collect
val strokePoint = StrokePoint(
x = p.x,
y = p.y,
pressure = 1f,
tiltX = 0,
tiltY = 0,
)
glRenderer.frontBufferRenderer?.renderFrontBufferedLayer(strokePoint)
}
}

// observe pen and stroke size
coroutineScope.launch {
Expand Down Expand Up @@ -852,36 +854,38 @@ class DrawCanvas(
}

private suspend fun updateIsDrawing() {
if(touchHelper == null) return
log.i("Update is drawing: ${state.isDrawing}")
if (state.isDrawing) {
touchHelper.setRawDrawingEnabled(true)
touchHelper!!.setRawDrawingEnabled(true)
} else {
// Check if drawing is completed
waitForDrawing()
// draw to view, before showing drawing, avoid stutter
drawCanvasToView(null)
touchHelper.setRawDrawingEnabled(false)
touchHelper!!.setRawDrawingEnabled(false)
}
}

fun updatePenAndStroke() {
if(touchHelper == null) return
// it takes around 11 ms to run on Note 4c.
log.i("Update pen and stroke")
when (state.mode) {
// we need to change size according to zoom level before drawing on screen
Mode.Draw, Mode.Line -> touchHelper.setStrokeStyle(penToStroke(state.pen))
Mode.Draw, Mode.Line -> touchHelper!!.setStrokeStyle(penToStroke(state.pen))
?.setStrokeWidth(state.penSettings[state.pen.penName]!!.strokeSize * page.zoomLevel.value)
?.setStrokeColor(state.penSettings[state.pen.penName]!!.color)

Mode.Erase -> {
when (state.eraser) {
Eraser.PEN -> touchHelper.setStrokeStyle(penToStroke(Pen.MARKER))
Eraser.PEN -> touchHelper!!.setStrokeStyle(penToStroke(Pen.MARKER))
?.setStrokeWidth(30f)
?.setStrokeColor(Color.GRAY)

Eraser.SELECT -> {
val dashStyleID = penToStroke(Pen.DASHED)
touchHelper.setStrokeStyle(dashStyleID)
touchHelper!!.setStrokeStyle(dashStyleID)
?.setStrokeWidth(3f)
?.setStrokeColor(Color.BLACK)
val params: FloatArray = FloatArray(4)
Expand All @@ -894,7 +898,7 @@ class DrawCanvas(
}
}

Mode.Select -> touchHelper.setStrokeStyle(penToStroke(Pen.BALLPEN))?.setStrokeWidth(3f)
Mode.Select -> touchHelper?.setStrokeStyle(penToStroke(Pen.BALLPEN))?.setStrokeWidth(3f)
?.setStrokeColor(Color.GRAY)
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/ethran/notable/editor/EditorView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ fun EditorView(


InkaTheme {
EditorGestureReceiver(controlTower = editorControlTower)
EditorSurface(
state = editorState, page = page, history = history
)
EditorGestureReceiver(controlTower = editorControlTower)
SelectedBitmap(
context = context,
controlTower = editorControlTower
Expand Down
35 changes: 23 additions & 12 deletions app/src/main/java/com/ethran/notable/editor/drawing/LineRenderer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import kotlin.concurrent.thread
import kotlin.math.max
import kotlin.math.min


class LineRenderer {
Expand Down Expand Up @@ -164,8 +166,12 @@ class LineRenderer {
GLES20.glUniform4fv(colorHandle, 1, colorArray, 0)
GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0)
vertexBuffer?.let { buffer ->
for (i in 0 until points.size - 1) {
var minX = Float.MAX_VALUE
var minY = Float.MAX_VALUE
var maxX = Float.MIN_VALUE
var maxY = Float.MIN_VALUE

for (i in 0 until points.size - 1) {
val p1 = points[i]
val p2 = points[i + 1]

Expand All @@ -176,25 +182,30 @@ class LineRenderer {
buffer.put(lineCoords)
buffer.position(0)

// Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(
positionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
VERTEX_STRIDE, buffer
)
// Render
GLES20.glDrawArrays(GLES20.GL_LINES, 0, VERTEX_COUNT)
val dirtyRect = Rect(
p1.x.toInt() - 20,
p1.y.toInt() - 20,
p1.x.toInt() + 20,
p1.y.toInt() + 20
)

GLES20.glDisableVertexAttribArray(positionHandle)
// EpdController.handwritingRepaint(viewModel, dirtyRect);
refreshScreenRegion(viewModel, dirtyRect)
minX = min(minX, min(p1.x, p2.x))
minY = min(minY, min(p1.y, p2.y))
maxX = max(maxX, max(p1.x, p2.x))
maxY = max(maxY, max(p1.y, p2.y))
}

val margin = 20
dirtyRect = Rect(
minX.toInt() - margin,
minY.toInt() - margin,
maxX.toInt() + margin,
maxY.toInt() + margin
)

GLES20.glDisableVertexAttribArray(positionHandle)
thread {
refreshScreenRegion(viewModel, dirtyRect)
}
}
GLES20.glDisableVertexAttribArray(positionHandle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class OpenGLRenderer(

timer.step("obtainRenderer")

obtainRenderer().drawSimpleLine(projection, pointsToDraw, Color.BLACK.toColor(), viewModel)
obtainRenderer().drawLine(projection, pointsToDraw, Color.BLACK.toColor(), viewModel)

timer.end("drawLine")
}
Expand Down Expand Up @@ -152,8 +152,10 @@ class OpenGLRenderer(
}

fun attachSurfaceView(surfaceView: SurfaceView) {
if (isAttached)
Log.w("OpenGLRenderer", "Already attached")
if (isAttached) {
Log.w("OpenGLRenderer", "Already attached, releasing old renderer first")
release()
}
frontBufferRenderer = GLFrontBufferedRenderer(surfaceView, this)
motionEventPredictor = MotionEventPredictor.newInstance(surfaceView)
}
Expand All @@ -162,10 +164,11 @@ class OpenGLRenderer(
get() = frontBufferRenderer != null

fun release() {
frontBufferRenderer?.release(true)
{
Log.d("OpenGLRenderer", "Releasing renderer")
frontBufferRenderer?.release(true) {
obtainRenderer().release()
}
frontBufferRenderer = null
}

private fun getStrokePoint(motionEvent: MotionEvent): StrokePoint {
Expand All @@ -179,7 +182,6 @@ class OpenGLRenderer(
)
}

// THIS DOES NOT GET ANY EVENTS, see EditorGestureReceiver.
@SuppressLint("ClickableViewAccessibility")
val onTouchListener = View.OnTouchListener { view, event ->
val point = getStrokePoint(event)
Expand All @@ -190,6 +192,8 @@ class OpenGLRenderer(

when (event?.action) {
MotionEvent.ACTION_DOWN -> {
// Clear leftover points from previous stroke
openGlPoints2.clear()
// Ask that the input system not batch MotionEvents
// but instead deliver them as soon as they're available
view.requestUnbufferedDispatch(event)
Expand Down
Loading
Loading