diff --git a/app/src/main/java/com/shezik/drawanywhere/DrawController.kt b/app/src/main/java/com/shezik/drawanywhere/DrawController.kt index 9e53eb3..d437661 100644 --- a/app/src/main/java/com/shezik/drawanywhere/DrawController.kt +++ b/app/src/main/java/com/shezik/drawanywhere/DrawController.kt @@ -29,7 +29,9 @@ import kotlinx.coroutines.flow.asStateFlow import java.util.UUID enum class PenType { - Pen, StrokeEraser, /*PixelEraser*/ // TODO + Pen, + Rectangle, // NEW TOOL + StrokeEraser, /*PixelEraser*/ // TODO } data class PenConfig( @@ -46,17 +48,23 @@ data class PathWrapper( private var cachedPathInvalid: MutableState = mutableStateOf(true), val color: Color, val width: Float, - val alpha: Float + val alpha: Float, + val smooth: Boolean = true ) { - val cachedPath: Path get() = - if ((_cachedPath.value == null) or cachedPathInvalid.value) - rebuildPath().value - else - _cachedPath.value!! + val cachedPath: Path + get() = + if ((_cachedPath.value == null) || cachedPathInvalid.value) + rebuildPath().value + else + _cachedPath.value!! @Suppress("UNCHECKED_CAST") - private fun rebuildPath(): MutableState { // TODO: Find a way to append points to the cached path instead of complete recalculation - _cachedPath.value = pointsToPath(points) + private fun rebuildPath(): MutableState { + _cachedPath.value = if (smooth) { + pointsToPath(points) + } else { + pointsToPolyline(points) + } cachedPathInvalid.value = false return _cachedPath as MutableState } @@ -109,7 +117,26 @@ class DrawController { } _pathList.lastOrNull()?.let { latestPath -> - latestPath.points.add(newPoint) + if (penConfig.penType == PenType.Rectangle) { + // Rectangle: build polygon from start point and current point + val start = latestPath.points.firstOrNull() ?: return + + val sx = start.x + val sy = start.y + val ex = newPoint.x + val ey = newPoint.y + + latestPath.points.clear() + latestPath.points.add(start) // top-left + latestPath.points.add(Offset(ex, sy)) // top-right + latestPath.points.add(Offset(ex, ey)) // bottom-right + latestPath.points.add(Offset(sx, ey)) // bottom-left + latestPath.points.add(start) // close polygon + } else { + // Freehand pen + latestPath.points.add(newPoint) + } + latestPath.invalidatePath() } } @@ -123,12 +150,15 @@ class DrawController { return } - _pathList.add(PathWrapper( - points = mutableStateListOf(newPoint), - color = penConfig.color, - width = penConfig.width, - alpha = penConfig.alpha - )) + _pathList.add( + PathWrapper( + points = mutableStateListOf(newPoint), + color = penConfig.color, + width = penConfig.width, + alpha = penConfig.alpha, + smooth = penConfig.penType != PenType.Rectangle + ) + ) } fun finishPath() { @@ -143,7 +173,8 @@ class DrawController { } redoStack.clear() - addToUndoStack(DrawAction.AddPath(latestPath)) // Shallow copy, we aren't touching its cachedPath. Undo/redo methods below depend on shallow copying. + // Shallow copy, we aren't touching its cachedPath. Undo/redo methods below depend on shallow copying. + addToUndoStack(DrawAction.AddPath(latestPath)) updateUndoRedoState() updateClearPathsState() } diff --git a/app/src/main/java/com/shezik/drawanywhere/DrawToolbar.kt b/app/src/main/java/com/shezik/drawanywhere/DrawToolbar.kt index a5673e4..01ecec0 100644 --- a/app/src/main/java/com/shezik/drawanywhere/DrawToolbar.kt +++ b/app/src/main/java/com/shezik/drawanywhere/DrawToolbar.kt @@ -40,6 +40,7 @@ import androidx.compose.material.icons.automirrored.filled.Redo import androidx.compose.material.icons.automirrored.filled.Undo import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.CropSquare import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -582,7 +583,8 @@ private fun PenTypeSelector( ) val penTypes = listOf( - PenType.Pen to stringResource(R.string.pen), + PenType.Rectangle to stringResource(R.string.rectangle), + PenType.Pen to stringResource(R.string.pen), PenType.StrokeEraser to stringResource(R.string.stroke_eraser) ) @@ -992,25 +994,29 @@ private fun createAllToolbarButtons( ), ToolbarButton( - id = "tool_controls", - icon = when (uiState.currentPenType) { - PenType.Pen -> Icons.Default.Edit - PenType.StrokeEraser -> InkEraser24Px - }, - contentDescription = stringResource(R.string.tool_controls), - popupPages = listOf( - { PenTypeSelector( - currentPenType = uiState.currentPenType, - onPenTypeSwitch = onPenTypeSwitch - ) }, - - { PenControls( - penConfig = uiState.currentPenConfig, - onStrokeWidthChange = onStrokeWidthChange, - onAlphaChange = onAlphaChange - ) } + id = "tool_controls", + icon = when (uiState.currentPenType) { + PenType.Rectangle -> Icons.Outlined.CropSquare + PenType.Pen -> Icons.Default.Edit + PenType.StrokeEraser -> InkEraser24Px + }, + contentDescription = stringResource(R.string.tool_controls), + popupPages = listOf( + { + PenTypeSelector( + currentPenType = uiState.currentPenType, + onPenTypeSwitch = onPenTypeSwitch ) - ), + }, + { + PenControls( + penConfig = uiState.currentPenConfig, + onStrokeWidthChange = onStrokeWidthChange, + onAlphaChange = onAlphaChange + ) + } + ) +), ToolbarButton( id = "color_picker", diff --git a/app/src/main/java/com/shezik/drawanywhere/DrawUtils.kt b/app/src/main/java/com/shezik/drawanywhere/DrawUtils.kt index 2d26ae5..de046e6 100644 --- a/app/src/main/java/com/shezik/drawanywhere/DrawUtils.kt +++ b/app/src/main/java/com/shezik/drawanywhere/DrawUtils.kt @@ -72,4 +72,14 @@ fun pointsToPath(points: List) = Path().apply { quadraticTo(start.x, start.y, mid.x, mid.y) } lineTo(points.last().x, points.last().y) +} + +fun pointsToPolyline(points: List) = Path().apply { + if (points.isEmpty()) + return@apply + + moveTo(points.first().x, points.first().y) + points.drop(1).forEach { point -> + lineTo(point.x, point.y) + } } \ No newline at end of file diff --git a/app/src/main/java/com/shezik/drawanywhere/DrawViewModel.kt b/app/src/main/java/com/shezik/drawanywhere/DrawViewModel.kt index 8659721..7c67b78 100644 --- a/app/src/main/java/com/shezik/drawanywhere/DrawViewModel.kt +++ b/app/src/main/java/com/shezik/drawanywhere/DrawViewModel.kt @@ -71,6 +71,7 @@ data class UiState( fun defaultPenConfigs(): Map = mapOf( PenType.Pen to PenConfig(penType = PenType.Pen), + PenType.Rectangle to PenConfig(penType = PenType.Rectangle), PenType.StrokeEraser to PenConfig(penType = PenType.StrokeEraser, width = 50f) ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index efd4e05..e753128 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,6 +8,7 @@ Expand toolbar Tools Pen + Rectangle Stroke Eraser Color Width