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
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package ja.burhanrashid52.photoeditor

import android.graphics.Paint
import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import ja.burhanrashid52.photoeditor.shape.OvalShape
import ja.burhanrashid52.photoeditor.shape.RectangleShape
import ja.burhanrashid52.photoeditor.shape.ShapeBuilder
import ja.burhanrashid52.photoeditor.shape.ShapeType
import junit.framework.TestCase.assertFalse
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
Expand Down Expand Up @@ -73,6 +79,64 @@ internal class DrawingViewTouchEventTest : BaseDrawingViewTest() {
assertTrue(redoPaths.empty())
}

@Test
fun testTapWithOvalDrawsUniformFilledCircle() {
val drawingView = setupDrawingView()
drawingView.currentShapeBuilder = ShapeBuilder().withShapeType(ShapeType.Oval)

tapView(drawingView, 150.0f, 100.0f)

val top = drawingView.drawingPath.first
assertFalse(top.empty())
assertTrue(top.peek()?.shape is OvalShape)
assertEquals(Paint.Style.FILL, top.peek()?.paint?.style)
}

@Test
fun testTapWithRectangleDrawsUniformFilledSquare() {
val drawingView = setupDrawingView()
drawingView.currentShapeBuilder = ShapeBuilder().withShapeType(ShapeType.Rectangle)

tapView(drawingView, 150.0f, 100.0f)

val top = drawingView.drawingPath.first
assertFalse(top.empty())
assertTrue(top.peek()?.shape is RectangleShape)
assertEquals(Paint.Style.FILL, top.peek()?.paint?.style)
}

@Test
fun testTapWithPointlessShapeDrawsNothing() {
for (shapeType in listOf(ShapeType.Line, ShapeType.Brush, ShapeType.Arrow())) {
val drawingView = setupDrawingView()
drawingView.currentShapeBuilder = ShapeBuilder().withShapeType(shapeType)

tapView(drawingView, 150.0f, 100.0f)

assertTrue("tap with $shapeType should draw nothing", drawingView.drawingPath.first.empty())
}
}
Comment thread
stephanepechard marked this conversation as resolved.

@Test
fun testTapWhileErasingDrawsNothing() {
val drawingView = setupDrawingView()
drawingView.brushEraser()

tapView(drawingView, 150.0f, 100.0f)

val drawnShapes = drawingView.drawingPath.first
assertTrue(drawnShapes.empty())
}
Comment thread
stephanepechard marked this conversation as resolved.

private fun tapView(drawingView: DrawingView, x: Float, y: Float) {
drawingView.dispatchTouchEvent(
MotionEvent.obtain(200, 300, MotionEvent.ACTION_DOWN, x, y, 0)
)
drawingView.dispatchTouchEvent(
MotionEvent.obtain(200, 300, MotionEvent.ACTION_UP, x, y, 0)
)
}

@Test
fun testPathDrawnOnTouchEvents() {
val brushViewChangeListener = Mockito.mock(BrushViewChangeListener::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.view.MotionEvent
import android.view.View
import ja.burhanrashid52.photoeditor.shape.*
import java.util.*
import kotlin.math.abs

/**
*
Expand Down Expand Up @@ -41,6 +42,11 @@ class DrawingView @JvmOverloads constructor(
private var isErasing = false
var eraserSize = DEFAULT_ERASER_SIZE

// tap detection: a touch that never moves beyond TAP_TOLERANCE is a tap, not a drag
private var touchDownX = 0f
private var touchDownY = 0f
private var hasMovedBeyondTap = false

// endregion
private fun createPaint(): Paint {
val paint = Paint()
Expand Down Expand Up @@ -110,11 +116,17 @@ class DrawingView @JvmOverloads constructor(
}

private fun onTouchEventDown(touchX: Float, touchY: Float) {
touchDownX = touchX
touchDownY = touchY
hasMovedBeyondTap = false
createShape()
currentShape?.shape?.startShape(touchX, touchY)
}

private fun onTouchEventMove(touchX: Float, touchY: Float) {
if (abs(touchX - touchDownX) >= TAP_TOLERANCE || abs(touchY - touchDownY) >= TAP_TOLERANCE) {
hasMovedBeyondTap = true
}
currentShape?.shape?.moveShape(touchX, touchY)
Comment thread
stephanepechard marked this conversation as resolved.
}

Expand Down Expand Up @@ -157,10 +169,8 @@ class DrawingView @JvmOverloads constructor(
}

private fun endShape(touchX: Float, touchY: Float) {
if (currentShape?.shape?.hasBeenTapped() == true) {
// just a tap, this is not a shape, so remove it
drawShapes.remove(currentShape)
//handleTap(touchX, touchY);
if (!hasMovedBeyondTap) {
handleTap(touchX, touchY)
}
viewChangeListener?.apply {
onStopDrawing()
Expand All @@ -171,6 +181,26 @@ class DrawingView @JvmOverloads constructor(
}
Comment thread
stephanepechard marked this conversation as resolved.
}

/**
* A single tap (no drag) drops a uniform filled version of the selected shape, sized from the
* shape thickness: a circle for an oval, a square for a rectangle. Shapes that can't be derived
* from a single point (line, arrow, freehand) and taps while erasing draw nothing, so the
* half-formed shape from the touch-down is discarded.
*/
private fun handleTap(touchX: Float, touchY: Float) {
drawShapes.remove(currentShape)
currentShape = null
if (isErasing) return
val size = currentShapeBuilder.shapeSize
val shape: AbstractShape = when (currentShapeBuilder.shapeType) {
ShapeType.Oval -> OvalShape().apply { drawCircle(touchX, touchY, size) }
ShapeType.Rectangle -> RectangleShape().apply { drawSquare(touchX, touchY, size) }
else -> return
}
currentShape = ShapeAndPaint(shape, createPaint().apply { style = Paint.Style.FILL })
drawShapes.push(currentShape)
}
Comment thread
stephanepechard marked this conversation as resolved.

fun undo(): Boolean {
if (!drawShapes.empty()) {
redoShapes.push(drawShapes.pop())
Expand Down Expand Up @@ -212,6 +242,7 @@ class DrawingView @JvmOverloads constructor(

companion object {
const val DEFAULT_ERASER_SIZE = 50.0f
private const val TAP_TOLERANCE = 4f
}

// region constructors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ class OvalShape : AbstractShape("OvalShape") {
return path
}

/** Build a uniform circle of [radius] centered at ([cx], [cy]), used when the shape is tapped. */
fun drawCircle(cx: Float, cy: Float, radius: Float) {
left = cx - radius
top = cy - radius
right = cx + radius
bottom = cy + radius
path = createOvalPath()
}

override fun stopShape() {
Log.d(tag, "stopShape")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ class RectangleShape : AbstractShape("RectangleShape") {
return path
}

/** Build a uniform square of half-side [halfSide] centered at ([cx], [cy]), used when tapped. */
fun drawSquare(cx: Float, cy: Float, halfSide: Float) {
left = cx - halfSide
top = cy - halfSide
right = cx + halfSide
bottom = cy + halfSide
path = createRectanglePath()
}

override fun stopShape() {
Log.d(tag, "stopShape")
}
Expand Down
Loading