[go: nahoru, domu]

blob: 1ec35d2a82d29c3ca70c5c9776253db665247369 [file] [log] [blame]
/*
* Copyright 2020 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 androidx.ui.core.pointerinput
import androidx.ui.core.ConsumedData
import androidx.ui.core.ExperimentalLayoutNodeApi
import androidx.ui.core.InternalPointerEvent
import androidx.ui.core.LayoutNode
import androidx.ui.core.PointerId
import androidx.ui.core.PointerInputChange
import androidx.ui.core.PointerInputData
import androidx.ui.core.anyPositionChangeConsumed
import androidx.ui.core.changedToDownIgnoreConsumed
import androidx.ui.core.changedToUpIgnoreConsumed
import androidx.ui.util.fastForEach
/**
* The core element that receives [PointerInputEvent]s and process them in Compose UI.
*/
@OptIn(ExperimentalLayoutNodeApi::class)
internal class PointerInputEventProcessor(val root: LayoutNode) {
private val hitPathTracker = HitPathTracker()
private val pointerInputChangeEventProducer = PointerInputChangeEventProducer()
/**
* Receives [PointerInputEvent]s and process them through the tree rooted on [root].
*
* @param pointerEvent The [PointerInputEvent] to process.
*
* @return the result of processing.
*
* @see ProcessResult
* @see PointerInputEvent
*/
fun process(pointerEvent: PointerInputEvent): ProcessResult {
// Gets a new PointerInputChangeEvent with the PointerInputEvent.
val internalPointerEvent =
pointerInputChangeEventProducer.produce(pointerEvent)
// TODO(shepshapard): Create fast forEach for maps?
// Add new hit paths to the tracker due to down events.
internalPointerEvent
.changes
.filter { (_, pointerInputChange) -> pointerInputChange.changedToDownIgnoreConsumed() }
.forEach { (_, pointerInputChange) ->
val hitResult: MutableList<PointerInputFilter> = mutableListOf()
root.hitTest(
pointerInputChange.current.position!!,
hitResult
)
if (hitResult.isNotEmpty()) {
hitPathTracker.addHitPath(pointerInputChange.id, hitResult)
}
}
// Remove [PointerInputFilter]s that are no longer valid and refresh the offset information
// for those that are.
hitPathTracker.removeDetachedPointerInputFilters()
// Dispatch to PointerInputFilters
val (resultingChanges, dispatchedToSomething) =
hitPathTracker.dispatchChanges(internalPointerEvent)
// Remove hit paths from the tracker due to up events.
internalPointerEvent
.changes
.filter { (_, pointerInputChange) -> pointerInputChange.changedToUpIgnoreConsumed() }
.forEach { (_, pointerInputChange) ->
hitPathTracker.removeHitPath(pointerInputChange.id)
}
// TODO(shepshapard): Don't allocate on every call.
return ProcessResult(
dispatchedToSomething,
resultingChanges.changes.any { (_, value) -> value.anyPositionChangeConsumed() })
}
/**
* Responds appropriately to Android ACTION_CANCEL events.
*
* Specifically, [PointerInputFilter.onCancel] is invoked on tracked [PointerInputFilter]s and
* and this [PointerInputEventProcessor] is reset such that it is no longer tracking any
* [PointerInputFilter]s and expects the next [PointerInputEvent] it processes to represent only
* new pointers.
*/
fun processCancel() {
pointerInputChangeEventProducer.clear()
hitPathTracker.processCancel()
}
}
/**
* Produces [InternalPointerEvent]s by tracking changes between [PointerInputEvent]s
*/
private class PointerInputChangeEventProducer {
private val previousPointerInputData: MutableMap<PointerId, PointerInputData> = mutableMapOf()
/**
* Produces [InternalPointerEvent]s by tracking changes between [PointerInputEvent]s
*/
internal fun produce(pointerInputEvent: PointerInputEvent):
InternalPointerEvent {
val changes: MutableMap<PointerId, PointerInputChange> = mutableMapOf()
pointerInputEvent.pointers.fastForEach {
changes[it.id] =
PointerInputChange(
it.id,
it.pointerInputData,
previousPointerInputData[it.id] ?: PointerInputData(),
ConsumedData()
)
if (it.pointerInputData.down) {
previousPointerInputData[it.id] = it.pointerInputData
} else {
previousPointerInputData.remove(it.id)
}
}
return InternalPointerEvent(changes, pointerInputEvent)
}
/**
* Clears all tracked information.
*/
internal fun clear() {
previousPointerInputData.clear()
}
}
/**
* The result of a call to [PointerInputEventProcessor.process].
*/
// TODO(shepshpard): Not sure if storing these values in a int is most efficient overall.
internal /*inline*/ data class ProcessResult internal constructor(private var value: Int) {
val dispatchedToAPointerInputModifier
get() = (value and 1) != 0
val anyMovementConsumed
get() = (value and (1 shl 1)) != 0
}
/**
* Constructs a new ProcessResult.
*
* @param dispatchedToAPointerInputModifier True if the dispatch resulted in at least 1
* [PointerInputModifier] receiving the event.
* @param anyMovementConsumed True if any movement occurred and was consumed.
*/
internal fun ProcessResult(
dispatchedToAPointerInputModifier: Boolean,
anyMovementConsumed: Boolean
): ProcessResult {
val val1 = if (dispatchedToAPointerInputModifier) 1 else 0
val val2 = if (anyMovementConsumed) (1 shl 1) else 0
return ProcessResult(val1 or val2)
}