blob: b5bd9ca746aacac0f95cafe2ff2151ec7d45801c [file] [log] [blame]
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package test.multideviceinput
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.InputDevice.SOURCE_STYLUS
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_DOWN
import android.view.MotionEvent.ACTION_HOVER_EXIT
import android.view.MotionEvent.ACTION_UP
import android.view.ScaleGestureDetector
import android.view.View
import java.util.Vector
private fun drawLine(canvas: Canvas, from: MotionEvent, to: MotionEvent, paint: Paint) {
// Correct implementation here would require us to build a set of pointers and then iterate
// through them. Instead, we are taking a few shortcuts and ignore some of the events, which
// causes occasional gaps in the drawings.
if (from.pointerCount != to.pointerCount) {
return
}
// Now, 'from' is guaranteed to have as many pointers as the 'to' event. It doesn't
// necessarily mean they are the same pointers, though.
for (p in 0..<from.pointerCount) {
val x0 = from.getX(p)
val y0 = from.getY(p)
if (to.getPointerId(p) == from.getPointerId(p)) {
// This only works when the i-th pointer in "to" is the same pointer
// as the i-th pointer in "from"`. It's not guaranteed by the input APIs,
// but it works in practice.
val x1 = to.getX(p)
val y1 = to.getY(p)
// Ignoring historical data here for simplicity
canvas.drawLine(x0, y0, x1, y1, paint)
}
}
}
private fun drawCircle(canvas: Canvas, event: MotionEvent, paint: Paint, radius: Float) {
val x = event.getX()
val y = event.getY()
canvas.drawCircle(x, y, radius, paint)
}
/**
* Draw the current stroke
*/
class DrawingView : View {
private val TAG = "DrawingView"
private var myState: SharedScaledPointerSize? = null
private var otherState: SharedScaledPointerSize? = null
constructor(
context: Context,
myState: SharedScaledPointerSize,
otherState: SharedScaledPointerSize
) : super(context) {
this.myState = myState
this.otherState = otherState
init()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init()
}
val touchEvents = mutableMapOf<Int, Vector<Pair<MotionEvent, Paint>>>()
val hoverEvents = mutableMapOf<Int, MotionEvent>()
val scaleGestureListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScaleBegin(scaleGestureDetector: ScaleGestureDetector): Boolean {
return true
}
override fun onScale(scaleGestureDetector: ScaleGestureDetector): Boolean {
val scaleFactor = scaleGestureDetector.scaleFactor
when (otherState?.state) {
PointerState.DOWN -> {
otherState?.lineSize = (otherState?.lineSize ?: 5f) * scaleFactor
}
PointerState.HOVER -> {
otherState?.circleSize = (otherState?.circleSize ?: 20f) * scaleFactor
}
else -> {}
}
return true
}
}
private val scaleGestureDetector = ScaleGestureDetector(context, scaleGestureListener, null)
private var touchPaint = Paint()
private var stylusPaint = Paint()
private fun init() {
touchPaint.color = Color.RED
touchPaint.setStrokeWidth(5f)
stylusPaint.color = Color.YELLOW
stylusPaint.setStrokeWidth(5f)
setOnHoverListener { _, event -> processHoverEvent(event); true }
}
private fun processTouchEvent(event: MotionEvent) {
scaleGestureDetector.onTouchEvent(event)
if (event.actionMasked == ACTION_DOWN) {
touchEvents.remove(event.deviceId)
myState?.state = PointerState.DOWN
} else if (event.actionMasked == ACTION_UP) {
myState?.state = PointerState.NONE
}
var vec = touchEvents.getOrPut(event.deviceId) { Vector<Pair<MotionEvent, Paint>>() }
val paint = if (event.isFromSource(SOURCE_STYLUS)) {
val size = myState?.lineSize ?: 5f
stylusPaint.setStrokeWidth(size)
Paint(stylusPaint)
} else {
val size = myState?.lineSize ?: 5f
touchPaint.setStrokeWidth(size)
Paint(touchPaint)
}
vec.add(Pair(MotionEvent.obtain(event), paint))
invalidate()
}
private fun processHoverEvent(event: MotionEvent) {
hoverEvents.remove(event.deviceId)
if (event.getActionMasked() != ACTION_HOVER_EXIT) {
hoverEvents.put(event.deviceId, MotionEvent.obtain(event))
myState?.state = PointerState.HOVER
} else {
myState?.state = PointerState.NONE
}
invalidate()
}
public override fun onTouchEvent(event: MotionEvent): Boolean {
processTouchEvent(event)
return true
}
public override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Draw touch and stylus MotionEvents
for ((_, vec) in touchEvents ) {
for (i in 1 until vec.size) {
drawLine(canvas, vec[i - 1].first, vec[i].first, vec[i].second)
}
}
// Draw hovers
for ((_, event) in hoverEvents ) {
if (event.isFromSource(SOURCE_STYLUS)) {
val size = myState?.circleSize ?: 20f
drawCircle(canvas, event, stylusPaint, size)
} else {
val size = myState?.circleSize ?: 20f
drawCircle(canvas, event, touchPaint, size)
}
}
}
}