Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ data class AppSettings(
val visualizePdfPagination: Boolean = false,
val paginatePdf: Boolean = true,
val scribbleToEraseEnabled: Boolean = false,
val smartLassoEnabled: Boolean = false,
val simpleRendering: Boolean = false,
val openGLRendering: Boolean = true,
val muPdfRendering: Boolean = true,
Expand Down
7 changes: 5 additions & 2 deletions app/src/main/java/com/ethran/notable/data/db/Kv.kt
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,21 @@ class KvRepository(context: Context) {
class KvProxy(context: Context) {
private val kvRepository = KvRepository(context)

// Configure JSON to ignore unknown keys for backward compatibility
private val json = Json { ignoreUnknownKeys = true }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to check I really want to enable it. Having it set to false makes sure that I know exact state of app config. For now this PR should not make this change.


fun <T> observeKv(key: String, serializer: KSerializer<T>, default: T): LiveData<T?> {
return kvRepository.getLive(key).map {
if (it == null) return@map default
val jsonValue = it.value
Json.decodeFromString(serializer, jsonValue)
json.decodeFromString(serializer, jsonValue)
}
}

fun <T> get(key: String, serializer: KSerializer<T>): T? {
val kv = kvRepository.get(key) ?: return null //returns null when there is no database
val jsonValue = kv.value
return Json.decodeFromString(serializer, jsonValue)
return json.decodeFromString(serializer, jsonValue)
}


Expand Down
30 changes: 23 additions & 7 deletions app/src/main/java/com/ethran/notable/editor/DrawCanvas.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import com.ethran.notable.editor.utils.handleDraw
import com.ethran.notable.editor.utils.handleErase
import com.ethran.notable.editor.utils.handleScribbleToErase
import com.ethran.notable.editor.utils.handleSelect
import com.ethran.notable.editor.utils.handleSmartLasso
import com.ethran.notable.editor.utils.onSurfaceChanged
import com.ethran.notable.editor.utils.onSurfaceDestroy
import com.ethran.notable.editor.utils.onSurfaceInit
Expand Down Expand Up @@ -305,6 +306,8 @@ class DrawCanvas(
val scaledPoints =
copyInput(plist.points, page.scroll, page.zoomLevel.value)
val firstPointTime = plist.points.first().timestamp

// First check for scribble-to-erase
val erasedByScribbleDirtyRect = handleScribbleToErase(
page,
scaledPoints,
Expand All @@ -313,17 +316,30 @@ class DrawCanvas(
currentLastStrokeEndTime,
firstPointTime
)

if (erasedByScribbleDirtyRect.isNullOrEmpty()) {
log.d("Drawing...")
// draw the stroke
handleDraw(
// Not a scribble-to-erase, check for smart lasso
val handledAsSmartLasso = handleSmartLasso(
coroutineScope,
this@DrawCanvas.page,
strokeHistoryBatch,
getActualState().penSettings[getActualState().pen.penName]!!.strokeSize,
getActualState().penSettings[getActualState().pen.penName]!!.color,
getActualState().pen,
getActualState(),
scaledPoints
)

if (!handledAsSmartLasso) {
// Neither scribble nor smart lasso, draw as regular stroke
log.d("Drawing...")
handleDraw(
this@DrawCanvas.page,
strokeHistoryBatch,
getActualState().penSettings[getActualState().pen.penName]!!.strokeSize,
getActualState().penSettings[getActualState().pen.penName]!!.color,
getActualState().pen,
scaledPoints
)
} else {
log.d("Handled as smart lasso selection")
}
} else {
log.d("Erased by scribble, $erasedByScribbleDirtyRect")
drawCanvasToView(erasedByScribbleDirtyRect)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.ethran.notable.editor

import android.content.Context
import android.util.Log
import androidx.compose.ui.geometry.Offset
import com.ethran.notable.TAG
import io.shipbook.shipbooksdk.Log
import com.ethran.notable.data.datastore.GlobalAppSettings
import com.ethran.notable.editor.state.EditorState
import com.ethran.notable.editor.state.History
Expand Down Expand Up @@ -146,6 +146,9 @@ class EditorControlTower(
}

fun deleteSelection() {
// Clear pending smart lasso data since user has committed to the selection action
clearPendingSmartLassoData()

val operationList = state.selectionState.deleteSelection(page)
history.addOperationsToHistory(operationList)
state.isDrawing = true
Expand All @@ -166,19 +169,28 @@ class EditorControlTower(
}

fun duplicateSelection() {
// Clear pending smart lasso data since user has committed to the selection action
clearPendingSmartLassoData()

// finish ongoing movement
applySelectionDisplace()
state.selectionState.duplicateSelection()

}

fun cutSelectionToClipboard(context: Context) {
// Clear pending smart lasso data since user has committed to the selection action
clearPendingSmartLassoData()

state.clipboard = state.selectionState.selectionToClipboard(page.scroll, context)
deleteSelection()
showHint("Content cut to clipboard", scope)
}

fun copySelectionToClipboard(context: Context) {
// Clear pending smart lasso data since user has committed to the selection action
clearPendingSmartLassoData()

state.clipboard = state.selectionState.selectionToClipboard(page.scroll, context)
}

Expand Down Expand Up @@ -230,5 +242,71 @@ class EditorControlTower(

showHint("Pasted content from clipboard", scope)
}

/**
* Clears all pending smart lasso data when user commits to a selection action
*/
private fun clearPendingSmartLassoData() {
state.selectionState.pendingSmartLassoStroke = null
state.selectionState.pendingSmartLassoPen = null
state.selectionState.pendingSmartLassoStrokeSize = null
state.selectionState.pendingSmartLassoColor = null
}

/**
* Dismisses the current selection. If the selection was from smart lasso,
* draws the original stroke with its original pen settings instead.
*/
fun dismissSelection() {
val pendingStroke = state.selectionState.pendingSmartLassoStroke
val pendingPen = state.selectionState.pendingSmartLassoPen
val pendingStrokeSize = state.selectionState.pendingSmartLassoStrokeSize
val pendingColor = state.selectionState.pendingSmartLassoColor

if (pendingStroke != null && pendingPen != null && pendingStrokeSize != null && pendingColor != null) {
Log.i("SmartLasso", "User dismissed smart lasso selection, drawing the original stroke with original pen settings")
// User dismissed without using the panel, so draw the original stroke with original settings
// Save the pending data before reset clears it
val strokeToDrawCopy = pendingStroke.toList()
val penCopy = pendingPen
val strokeSizeCopy = pendingStrokeSize
val colorCopy = pendingColor

// Reset the selection
state.selectionState.reset()

// Now draw the pending stroke with its original pen settings
val strokeHistoryBatch = mutableListOf<String>()
com.ethran.notable.editor.utils.handleDraw(
page,
strokeHistoryBatch,
strokeSizeCopy,
colorCopy,
penCopy,
strokeToDrawCopy
)

// Add to history
if (strokeHistoryBatch.isNotEmpty()) {
history.addOperationsToHistory(
operations = listOf(
Operation.DeleteStroke(strokeHistoryBatch)
)
)
}

state.isDrawing = true

// Refresh UI
scope.launch {
DrawCanvas.refreshUi.emit(Unit)
}
Comment on lines +267 to +303
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be moved to separate funtion

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it should be done as new operation list in History(pageView: PageView),

  private var uncommittedOperations: OperationList = mutableListOf()

And there should be functions:
commitOperations()
addToUncommittedOperations(…)
clearUncommittedOperations(…)

(There probably are better names for them)

See also branch dev, I made recently some changes how the history is handled (I needed to handled one thing differently, and ended up changing a lot):
Commit: 57d5fe2
It will be merged when I address most of TODO's in this marge request. #156

} else {
// Normal selection dismissal
applySelectionDisplace()
state.selectionState.reset()
state.isDrawing = true
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import androidx.core.graphics.createBitmap
import com.ethran.notable.TAG
import com.ethran.notable.data.db.Image
import com.ethran.notable.data.db.Stroke
import com.ethran.notable.data.db.StrokePoint
import com.ethran.notable.data.model.SimplePointF
import com.ethran.notable.editor.PageView
import com.ethran.notable.editor.drawing.drawImage
import com.ethran.notable.editor.utils.Pen
import com.ethran.notable.editor.utils.imageBoundsInt
import com.ethran.notable.editor.utils.offsetImage
import com.ethran.notable.editor.utils.offsetStroke
Expand All @@ -35,6 +37,13 @@ class SelectionState {
var selectedStrokes by mutableStateOf<List<Stroke>?>(null)
var selectedImages by mutableStateOf<List<Image>?>(null)

// Smart lasso: stores the original stroke data if selection was made via smart lasso
// This allows fallback to drawing the stroke if user dismisses without using the panel
var pendingSmartLassoStroke by mutableStateOf<List<StrokePoint>?>(null)
var pendingSmartLassoPen by mutableStateOf<Pen?>(null)
var pendingSmartLassoStrokeSize by mutableStateOf<Float?>(null)
var pendingSmartLassoColor by mutableStateOf<Int?>(null)
Comment on lines +40 to +45
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the place for those, see other comments.


// TODO: Bitmap should be change, if scale changes.
var selectedBitmap by mutableStateOf<Bitmap?>(null)

Expand All @@ -53,6 +62,10 @@ class SelectionState {
selectionStartOffset = null
selectionDisplaceOffset = null
placementMode = null
pendingSmartLassoStroke = null
pendingSmartLassoPen = null
pendingSmartLassoStrokeSize = null
pendingSmartLassoColor = null
setAnimationMode(false)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ fun SelectedBitmap(
Modifier
.fillMaxSize()
.noRippleClickable {
controlTower.applySelectionDisplace()
selectionState.reset()
editorState.isDrawing = true
// Delegate dismissal logic to control tower
controlTower.dismissSelection()
}) {
Image(
bitmap = selectionState.selectedBitmap!!.asImageBitmap(),
Expand Down
Loading