blob: 23153be7289086c70b5d556375221bd8e72fc1e4 [file] [log] [blame]
/*
* 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);
}
}
}