Added stylus-only mode for PDF drawing and annotations. (#27)

This change introduces a "Stylus Only Mode" toggle in the annotation dock, allowing users to restrict drawing input to stylus pens while maintaining touch support for scrolling and navigation.

Key changes include:
- Added `isStylusOnlyMode` state and persistence logic in `PdfViewerScreen`.
- Updated `AnnotationDock` with a toggle button for the new mode.
- Modified pointer input handling in `PdfPageComposable` and `PdfVerticalReader` to filter touch events when stylus-only mode is active.
- Adjusted tap gesture detection to ensure scrolling and page clicks still function correctly under various input modes.
This commit is contained in:
Aryan 2026-03-04 13:18:26 +05:30 committed by GitHub
parent d459535ffd
commit b47b6a9164
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 97 additions and 25 deletions

View file

@ -88,6 +88,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChanged
import androidx.compose.ui.input.pointer.util.VelocityTracker
@ -216,7 +217,8 @@ internal fun PdfVerticalReader(
isAutoScrollTempPaused: Boolean = false,
isScrollLocked: Boolean = false,
autoScrollSpeed: Float = 1.0f,
onInteractionListener: () -> Unit = {}
onInteractionListener: () -> Unit = {},
isStylusOnlyMode: Boolean = false
) {
SideEffect { Timber.tag("PdfDrawPerf").v("LIST: PdfVerticalReader Recomposing.") }
var globalEraserPosition by remember { mutableStateOf<Offset?>(null) }
@ -726,13 +728,17 @@ internal fun PdfVerticalReader(
}
}
val globalDrawingModifier = Modifier.pointerInput(isEditMode, layoutInfo, selectedTool) {
val globalDrawingModifier = Modifier.pointerInput(isEditMode, layoutInfo, selectedTool, isStylusOnlyMode) {
if (!isEditMode) return@pointerInput
if (selectedTool == InkType.TEXT) return@pointerInput
awaitEachGesture {
val down = awaitFirstDown(requireUnconsumed = false)
if (isStylusOnlyMode && down.type == PointerType.Touch) {
return@awaitEachGesture
}
fun getPageAndPoint(screenOffset: Offset): Pair<Int, PdfPoint>? {
val zoom = zoomAnimatable.value
val panX = panXAnimatable.value
@ -814,31 +820,31 @@ internal fun PdfVerticalReader(
modifier = Modifier
.fillMaxSize()
.then(globalDrawingModifier)
.pointerInput(isEditMode, selectedTool) {
.pointerInput(isEditMode, selectedTool, isStylusOnlyMode) {
Timber.tag("PdfTouchDebug").v(
"VerticalReader: TapPointerInput init. isEditMode=$isEditMode"
)
if (isEditMode && selectedTool != InkType.TEXT) return@pointerInput
val isTapDetectionAllowed = !isEditMode ||
selectedTool == InkType.TEXT ||
isStylusOnlyMode
if (!isTapDetectionAllowed) return@pointerInput
detectTapGestures(onTap = {
if (!isEditMode) {
Timber.tag("PdfTouchDebug").d(
"VerticalReader: Tap detected (Toggling bars)"
)
Timber.tag("PdfTouchDebug").d("VerticalReader: Tap detected")
selectionClearTrigger++
onPageClick()
} else if (selectedTool == InkType.TEXT) {
onPageClick()
}
}, onDoubleTap = { offset ->
if (!isEditMode) {
Timber.tag("PdfTouchDebug").d("VerticalReader: DoubleTap detected")
onDoubleTapToZoom(offset)
}
Timber.tag("PdfTouchDebug").d("VerticalReader: DoubleTap detected")
onDoubleTapToZoom(offset)
})
}
.pointerInput(totalDocHeight, isEditMode, selectedTool, isScrollLocked) {
.pointerInput(totalDocHeight, isEditMode, selectedTool, isScrollLocked, isStylusOnlyMode) {
val tracker = VelocityTracker()
val decay = exponentialDecay<Float>()
val touchSlop = viewConfiguration.touchSlop
@ -849,11 +855,14 @@ internal fun PdfVerticalReader(
)
val down = awaitFirstDown(requireUnconsumed = false)
Timber.tag("PdfTouchDebug").v(
"VerticalReader: DOWN received. Pressed=${down.pressed}, Consumed=${down.isConsumed}"
)
if (isEditMode && selectedTool != InkType.TEXT && down.pressed) {
Timber.tag("PointerTypeDebug").d("VerticalReader: Input Type detected: ${down.type}")
val isDrawingGesture = isEditMode &&
selectedTool != InkType.TEXT &&
(!isStylusOnlyMode || down.type != PointerType.Touch)
if (isDrawingGesture && down.pressed) {
val event = awaitPointerEvent()
Timber.tag("PdfTouchDebug").v(
"VerticalReader: EditMode check. Event changes: ${event.changes.size}"
@ -959,8 +968,9 @@ internal fun PdfVerticalReader(
}
}
val shouldScroll =
(panLocked || gestureDisambiguationMode != 0) && (!isEditMode || selectedTool == InkType.TEXT || isMultiTouch)
val isTouchInput = event.changes.all { it.type == PointerType.Touch }
val shouldScroll = (panLocked || gestureDisambiguationMode != 0) &&
(!isEditMode || selectedTool == InkType.TEXT || isMultiTouch || (isStylusOnlyMode && isTouchInput))
if (shouldScroll) {
if (!isInteracting) {
@ -1432,6 +1442,7 @@ internal fun PdfVerticalReader(
onOcrModelDownloading = onOcrModelDownloading,
selectedTool = selectedTool,
richTextController = richTextController,
isStylusOnlyMode = isStylusOnlyMode,
textBoxes = textBoxes.filter { it.pageIndex == page.index },
selectedTextBoxId = selectedTextBoxId,
onTextBoxChange = onTextBoxChange,