| /* |
| * Copyright 2019 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.gesture |
| |
| import androidx.compose.remember |
| import androidx.ui.core.DensityAmbient |
| import androidx.ui.core.Modifier |
| import androidx.ui.core.PointerEventPass |
| import androidx.ui.core.PointerInputChange |
| import androidx.ui.core.changedToUpIgnoreConsumed |
| import androidx.ui.core.composed |
| import androidx.ui.core.pointerinput.PointerInputFilter |
| import androidx.ui.unit.IntSize |
| import kotlin.math.absoluteValue |
| |
| // TODO(b/143877464): Implement a "can scale in / can scale out" check so that scale slop is only |
| // surpassed in the appropriate direction? |
| /** |
| * This gesture detector detects when a user's pointer input is intended to include scaling. |
| * |
| * This gesture detector is very similar to [rawScaleGestureFilter] except that instead of |
| * providing callbacks for scaling, it instead provides one callback for when a user is intending |
| * to scale. It does so using the same semantics as [rawScaleGestureFilter], and simply waits |
| * until the user has scaled just enough to suggest the user is truly intended to scale. |
| * |
| * The gesture is considered to include scaling when the absolute cumulative average change in |
| * distance of all pointers from the average pointer over time surpasses a particular value |
| * (currently [ScaleSlop]). |
| * |
| * For example, if the [ScaleSlop] is 5 pixels and 2 pointers were 1 pixel away from each |
| * other and now are 11.00001 pixels away from each other, the slop will have been surpassed and |
| * [onScaleSlopExceeded] will be called (both pointers are slightly more than 5 pixels away from |
| * the average of the pointers than they were). |
| */ |
| fun Modifier.scaleSlopExceededGestureFilter( |
| onScaleSlopExceeded: () -> Unit |
| ): Modifier = composed { |
| val scaleSlop = with(DensityAmbient.current) { ScaleSlop.toPx() } |
| val filter = remember { ScaleSlopExceededGestureFilter(scaleSlop) } |
| // TODO(b/129784010): Consider also allowing onStart, onScale, and onEnd to be set individually. |
| filter.onScaleSlopExceeded = onScaleSlopExceeded |
| PointerInputModifierImpl(filter) |
| } |
| |
| /** |
| * @param scaleSlop The absolute cumulative average change in distance of all pointers from the |
| * average pointer over time that must be surpassed to indicate the user is trying to scale. |
| * |
| * @see scaleSlopExceededGestureFilter |
| */ |
| internal class ScaleSlopExceededGestureFilter(private val scaleSlop: Float) : PointerInputFilter |
| () { |
| lateinit var onScaleSlopExceeded: () -> Unit |
| |
| var passedSlop = false |
| var scaleDiffTotal = 0f |
| |
| override fun onPointerInput( |
| changes: List<PointerInputChange>, |
| pass: PointerEventPass, |
| bounds: IntSize |
| ): List<PointerInputChange> { |
| |
| if (pass == PointerEventPass.PostUp) { |
| |
| if (!passedSlop) { |
| |
| val currentlyDownChanges = |
| changes.filter { it.current.down && it.previous.down } |
| |
| if (currentlyDownChanges.isNotEmpty()) { |
| val dimensionInformation = |
| currentlyDownChanges.calculateAllDimensionInformation() |
| val scaleDifference = dimensionInformation.calculateScaleDifference() |
| |
| scaleDiffTotal += scaleDifference |
| |
| if (scaleDiffTotal.absoluteValue > scaleSlop) { |
| passedSlop = true |
| onScaleSlopExceeded.invoke() |
| } |
| } |
| } |
| } |
| |
| if (passedSlop && |
| pass == PointerEventPass.PostDown && |
| changes.all { it.changedToUpIgnoreConsumed() } |
| ) { |
| passedSlop = false |
| scaleDiffTotal = 0f |
| } |
| |
| return changes |
| } |
| |
| override fun onCancel() { |
| passedSlop = false |
| scaleDiffTotal = 0f |
| } |
| } |