| /* |
| * Copyright (C) 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 com.android.wm.shell.pip.phone; |
| |
| import android.graphics.Point; |
| import android.graphics.PointF; |
| import android.graphics.Rect; |
| |
| /** |
| * Helper class to calculate the new size given two-fingers pinch to resize. |
| */ |
| public class PipPinchResizingAlgorithm { |
| |
| private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45; |
| private static final float OVERROTATE_DAMP_FACTOR = 0.4f; |
| private static final float ANGLE_THRESHOLD = 5f; |
| private static final float OVERRESIZE_DAMP_FACTOR = 0.25f; |
| |
| private final PointF mTmpDownVector = new PointF(); |
| private final PointF mTmpLastVector = new PointF(); |
| private final PointF mTmpDownCentroid = new PointF(); |
| private final PointF mTmpLastCentroid = new PointF(); |
| |
| /** |
| * Updates {@param resizeBoundsOut} with the new bounds of the PIP, and returns the angle in |
| * degrees that the PIP should be rotated. |
| */ |
| public float calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint, |
| PointF lastPoint, PointF lastSecondPoint, Point minSize, Point maxSize, |
| Rect initialBounds, Rect resizeBoundsOut) { |
| float downDist = (float) Math.hypot(downSecondPoint.x - downPoint.x, |
| downSecondPoint.y - downPoint.y); |
| float dist = (float) Math.hypot(lastSecondPoint.x - lastPoint.x, |
| lastSecondPoint.y - lastPoint.y); |
| float minScale = getMinScale(initialBounds, minSize); |
| float maxScale = getMaxScale(initialBounds, maxSize); |
| float overStretchMin = minScale - dist / downDist > 0 ? minScale - dist / downDist : 0; |
| float overStretchMax = dist / downDist - maxScale > 0 ? dist / downDist - maxScale : 0; |
| float scale = Math.max(minScale - overStretchMin * OVERRESIZE_DAMP_FACTOR, |
| Math.min(maxScale + overStretchMax * OVERRESIZE_DAMP_FACTOR, dist / downDist)); |
| |
| // Scale the bounds by the change in distance between the points |
| resizeBoundsOut.set(initialBounds); |
| scaleRectAboutCenter(resizeBoundsOut, scale); |
| |
| // Translate by the centroid movement |
| getCentroid(downPoint, downSecondPoint, mTmpDownCentroid); |
| getCentroid(lastPoint, lastSecondPoint, mTmpLastCentroid); |
| resizeBoundsOut.offset((int) (mTmpLastCentroid.x - mTmpDownCentroid.x), |
| (int) (mTmpLastCentroid.y - mTmpDownCentroid.y)); |
| |
| // Calculate the angle |
| mTmpDownVector.set(downSecondPoint.x - downPoint.x, |
| downSecondPoint.y - downPoint.y); |
| mTmpLastVector.set(lastSecondPoint.x - lastPoint.x, |
| lastSecondPoint.y - lastPoint.y); |
| float angle = (float) Math.atan2(cross(mTmpDownVector, mTmpLastVector), |
| dot(mTmpDownVector, mTmpLastVector)); |
| return constrainRotationAngle((float) Math.toDegrees(angle)); |
| } |
| |
| private float getMinScale(Rect bounds, Point minSize) { |
| return Math.max((float) minSize.x / bounds.width(), (float) minSize.y / bounds.height()); |
| } |
| |
| private float getMaxScale(Rect bounds, Point maxSize) { |
| return Math.min((float) maxSize.x / bounds.width(), (float) maxSize.y / bounds.height()); |
| } |
| |
| private float constrainRotationAngle(float angle) { |
| // Remove some degrees so that user doesn't immediately start rotating until a threshold |
| return Math.signum(angle) * Math.max(0, (Math.abs(dampedRotate(angle)) - ANGLE_THRESHOLD)); |
| } |
| |
| /** |
| * Given the current rotation angle, dampen it so that as it approaches the maximum angle, |
| * dampen it. |
| */ |
| private float dampedRotate(float amount) { |
| if (Float.compare(amount, 0) == 0) return 0; |
| |
| float f = amount / PINCH_RESIZE_MAX_ANGLE_ROTATION; |
| f = f / (Math.abs(f)) * (overRotateInfluenceCurve(Math.abs(f))); |
| |
| // Clamp this factor, f, to -1 < f < 1 |
| if (Math.abs(f) >= 1) { |
| f /= Math.abs(f); |
| } |
| return OVERROTATE_DAMP_FACTOR * f * PINCH_RESIZE_MAX_ANGLE_ROTATION; |
| } |
| |
| /** |
| * Returns a value that corresponds to y = (f - 1)^3 + 1. |
| */ |
| private float overRotateInfluenceCurve(float f) { |
| f -= 1.0f; |
| return f * f * f + 1.0f; |
| } |
| |
| private void getCentroid(PointF p1, PointF p2, PointF centroidOut) { |
| centroidOut.set((p2.x + p1.x) / 2, (p2.y + p1.y) / 2); |
| } |
| |
| private float dot(PointF p1, PointF p2) { |
| return p1.x * p2.x + p1.y * p2.y; |
| } |
| |
| private float cross(PointF p1, PointF p2) { |
| return p1.x * p2.y - p1.y * p2.x; |
| } |
| |
| private void scaleRectAboutCenter(Rect r, float scale) { |
| if (scale != 1.0f) { |
| int cx = r.centerX(); |
| int cy = r.centerY(); |
| r.offset(-cx, -cy); |
| r.scale(scale); |
| r.offset(cx, cy); |
| } |
| } |
| } |