1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.wm.shell.common.pip; 17 18 import android.graphics.Point; 19 import android.graphics.PointF; 20 import android.graphics.Rect; 21 22 /** 23 * Helper class to calculate the new size given two-fingers pinch to resize. 24 */ 25 public class PipPinchResizingAlgorithm { 26 27 private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45; 28 private static final float OVERROTATE_DAMP_FACTOR = 0.4f; 29 private static final float ANGLE_THRESHOLD = 5f; 30 private static final float OVERRESIZE_DAMP_FACTOR = 0.25f; 31 32 private final PointF mTmpDownVector = new PointF(); 33 private final PointF mTmpLastVector = new PointF(); 34 private final PointF mTmpDownCentroid = new PointF(); 35 private final PointF mTmpLastCentroid = new PointF(); 36 37 /** 38 * Updates resizeBoundsOut with the new bounds of the PIP, and returns the angle in 39 * degrees that the PIP should be rotated. 40 */ calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint, PointF lastPoint, PointF lastSecondPoint, Point minSize, Point maxSize, Rect initialBounds, Rect resizeBoundsOut)41 public float calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint, 42 PointF lastPoint, PointF lastSecondPoint, Point minSize, Point maxSize, 43 Rect initialBounds, Rect resizeBoundsOut) { 44 float downDist = (float) Math.hypot(downSecondPoint.x - downPoint.x, 45 downSecondPoint.y - downPoint.y); 46 float dist = (float) Math.hypot(lastSecondPoint.x - lastPoint.x, 47 lastSecondPoint.y - lastPoint.y); 48 float minScale = getMinScale(initialBounds, minSize); 49 float maxScale = getMaxScale(initialBounds, maxSize); 50 float overStretchMin = minScale - dist / downDist > 0 ? minScale - dist / downDist : 0; 51 float overStretchMax = dist / downDist - maxScale > 0 ? dist / downDist - maxScale : 0; 52 float scale = Math.max(minScale - overStretchMin * OVERRESIZE_DAMP_FACTOR, 53 Math.min(maxScale + overStretchMax * OVERRESIZE_DAMP_FACTOR, dist / downDist)); 54 55 // Scale the bounds by the change in distance between the points 56 resizeBoundsOut.set(initialBounds); 57 scaleRectAboutCenter(resizeBoundsOut, scale); 58 59 // Translate by the centroid movement 60 getCentroid(downPoint, downSecondPoint, mTmpDownCentroid); 61 getCentroid(lastPoint, lastSecondPoint, mTmpLastCentroid); 62 resizeBoundsOut.offset((int) (mTmpLastCentroid.x - mTmpDownCentroid.x), 63 (int) (mTmpLastCentroid.y - mTmpDownCentroid.y)); 64 65 // Calculate the angle 66 mTmpDownVector.set(downSecondPoint.x - downPoint.x, 67 downSecondPoint.y - downPoint.y); 68 mTmpLastVector.set(lastSecondPoint.x - lastPoint.x, 69 lastSecondPoint.y - lastPoint.y); 70 float angle = (float) Math.atan2(cross(mTmpDownVector, mTmpLastVector), 71 dot(mTmpDownVector, mTmpLastVector)); 72 return constrainRotationAngle((float) Math.toDegrees(angle)); 73 } 74 getMinScale(Rect bounds, Point minSize)75 private float getMinScale(Rect bounds, Point minSize) { 76 return Math.max((float) minSize.x / bounds.width(), (float) minSize.y / bounds.height()); 77 } 78 getMaxScale(Rect bounds, Point maxSize)79 private float getMaxScale(Rect bounds, Point maxSize) { 80 return Math.min((float) maxSize.x / bounds.width(), (float) maxSize.y / bounds.height()); 81 } 82 constrainRotationAngle(float angle)83 private float constrainRotationAngle(float angle) { 84 // Remove some degrees so that user doesn't immediately start rotating until a threshold 85 return Math.signum(angle) * Math.max(0, (Math.abs(dampedRotate(angle)) - ANGLE_THRESHOLD)); 86 } 87 88 /** 89 * Given the current rotation angle, dampen it so that as it approaches the maximum angle, 90 * dampen it. 91 */ dampedRotate(float amount)92 private float dampedRotate(float amount) { 93 if (Float.compare(amount, 0) == 0) return 0; 94 95 float f = amount / PINCH_RESIZE_MAX_ANGLE_ROTATION; 96 f = f / (Math.abs(f)) * (overRotateInfluenceCurve(Math.abs(f))); 97 98 // Clamp this factor, f, to -1 < f < 1 99 if (Math.abs(f) >= 1) { 100 f /= Math.abs(f); 101 } 102 return OVERROTATE_DAMP_FACTOR * f * PINCH_RESIZE_MAX_ANGLE_ROTATION; 103 } 104 105 /** 106 * Returns a value that corresponds to y = (f - 1)^3 + 1. 107 */ overRotateInfluenceCurve(float f)108 private float overRotateInfluenceCurve(float f) { 109 f -= 1.0f; 110 return f * f * f + 1.0f; 111 } 112 getCentroid(PointF p1, PointF p2, PointF centroidOut)113 private void getCentroid(PointF p1, PointF p2, PointF centroidOut) { 114 centroidOut.set((p2.x + p1.x) / 2, (p2.y + p1.y) / 2); 115 } 116 dot(PointF p1, PointF p2)117 private float dot(PointF p1, PointF p2) { 118 return p1.x * p2.x + p1.y * p2.y; 119 } 120 cross(PointF p1, PointF p2)121 private float cross(PointF p1, PointF p2) { 122 return p1.x * p2.y - p1.y * p2.x; 123 } 124 scaleRectAboutCenter(Rect r, float scale)125 private void scaleRectAboutCenter(Rect r, float scale) { 126 if (scale != 1.0f) { 127 int cx = r.centerX(); 128 int cy = r.centerY(); 129 r.offset(-cx, -cy); 130 r.scale(scale); 131 r.offset(cx, cy); 132 } 133 } 134 } 135