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 17 package com.android.wm.shell.common.pip; 18 19 import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT; 20 import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; 21 import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT; 22 23 import android.graphics.Rect; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 27 /** 28 * Calculates the snap targets and the snap position for the PIP given a position and a velocity. 29 * All bounds are relative to the display top/left. 30 */ 31 public class PipSnapAlgorithm { 32 33 /** 34 * Returns a fraction that describes where the PiP bounds is. 35 * See {@link #getSnapFraction(Rect, Rect, int)}. 36 */ getSnapFraction(Rect stackBounds, Rect movementBounds)37 public float getSnapFraction(Rect stackBounds, Rect movementBounds) { 38 return getSnapFraction(stackBounds, movementBounds, STASH_TYPE_NONE); 39 } 40 41 /** 42 * @return returns a fraction that describes where along the movementBounds the 43 * stackBounds are. If the stackBounds are not currently on the 44 * movementBounds exactly, then they will be snapped to the movement bounds. 45 * stashType dictates whether the PiP is stashed (off-screen) or not. If 46 * that's the case, we will have to do some math to calculate the snap fraction 47 * correctly. 48 * 49 * The fraction is defined in a clockwise fashion against the movementBounds: 50 * 51 * 0 1 52 * 4 +---+ 1 53 * | | 54 * 3 +---+ 2 55 * 3 2 56 */ getSnapFraction(Rect stackBounds, Rect movementBounds, @PipBoundsState.StashType int stashType)57 public float getSnapFraction(Rect stackBounds, Rect movementBounds, 58 @PipBoundsState.StashType int stashType) { 59 final Rect tmpBounds = new Rect(); 60 snapRectToClosestEdge(stackBounds, movementBounds, tmpBounds, stashType); 61 final float widthFraction = (float) (tmpBounds.left - movementBounds.left) 62 / movementBounds.width(); 63 final float heightFraction = (float) (tmpBounds.top - movementBounds.top) 64 / movementBounds.height(); 65 if (tmpBounds.top == movementBounds.top) { 66 return widthFraction; 67 } else if (tmpBounds.left == movementBounds.right) { 68 return 1f + heightFraction; 69 } else if (tmpBounds.top == movementBounds.bottom) { 70 return 2f + (1f - widthFraction); 71 } else { 72 return 3f + (1f - heightFraction); 73 } 74 } 75 76 /** 77 * Moves the stackBounds along the movementBounds to the given snap fraction. 78 * See {@link #getSnapFraction(Rect, Rect)}. 79 * 80 * The fraction is define in a clockwise fashion against the movementBounds: 81 * 82 * 0 1 83 * 4 +---+ 1 84 * | | 85 * 3 +---+ 2 86 * 3 2 87 */ applySnapFraction(Rect stackBounds, Rect movementBounds, float snapFraction)88 public void applySnapFraction(Rect stackBounds, Rect movementBounds, float snapFraction) { 89 if (snapFraction < 1f) { 90 int offset = movementBounds.left + (int) (snapFraction * movementBounds.width()); 91 stackBounds.offsetTo(offset, movementBounds.top); 92 } else if (snapFraction < 2f) { 93 snapFraction -= 1f; 94 int offset = movementBounds.top + (int) (snapFraction * movementBounds.height()); 95 stackBounds.offsetTo(movementBounds.right, offset); 96 } else if (snapFraction < 3f) { 97 snapFraction -= 2f; 98 int offset = movementBounds.left + (int) ((1f - snapFraction) * movementBounds.width()); 99 stackBounds.offsetTo(offset, movementBounds.bottom); 100 } else { 101 snapFraction -= 3f; 102 int offset = movementBounds.top + (int) ((1f - snapFraction) * movementBounds.height()); 103 stackBounds.offsetTo(movementBounds.left, offset); 104 } 105 } 106 107 /** 108 * Same as {@link #applySnapFraction(Rect, Rect, float)}, but take stash state into 109 * consideration. 110 */ applySnapFraction(Rect stackBounds, Rect movementBounds, float snapFraction, @PipBoundsState.StashType int stashType, int stashOffset, Rect displayBounds, Rect insetBounds)111 public void applySnapFraction(Rect stackBounds, Rect movementBounds, float snapFraction, 112 @PipBoundsState.StashType int stashType, int stashOffset, Rect displayBounds, 113 Rect insetBounds) { 114 applySnapFraction(stackBounds, movementBounds, snapFraction); 115 116 if (stashType != STASH_TYPE_NONE) { 117 stackBounds.offsetTo(stashType == STASH_TYPE_LEFT 118 ? stashOffset - stackBounds.width() + insetBounds.left 119 : displayBounds.right - stashOffset - insetBounds.right, 120 stackBounds.top); 121 } 122 } 123 124 /** 125 * Snaps the stackBounds to the closest edge of the movementBounds and writes 126 * the new bounds out to boundsOut. 127 */ 128 @VisibleForTesting snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut, @PipBoundsState.StashType int stashType)129 public void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut, 130 @PipBoundsState.StashType int stashType) { 131 int leftEdge = stackBounds.left; 132 if (stashType == STASH_TYPE_LEFT) { 133 leftEdge = movementBounds.left; 134 } else if (stashType == STASH_TYPE_RIGHT) { 135 leftEdge = movementBounds.right; 136 } 137 final int boundedLeft = Math.max(movementBounds.left, Math.min(movementBounds.right, 138 leftEdge)); 139 final int boundedTop = Math.max(movementBounds.top, Math.min(movementBounds.bottom, 140 stackBounds.top)); 141 boundsOut.set(stackBounds); 142 143 // Otherwise, just find the closest edge 144 final int fromLeft = Math.abs(leftEdge - movementBounds.left); 145 final int fromTop = Math.abs(stackBounds.top - movementBounds.top); 146 final int fromRight = Math.abs(movementBounds.right - leftEdge); 147 final int fromBottom = Math.abs(movementBounds.bottom - stackBounds.top); 148 final int shortest = Math.min(Math.min(fromLeft, fromRight), Math.min(fromTop, fromBottom)); 149 if (shortest == fromLeft) { 150 boundsOut.offsetTo(movementBounds.left, boundedTop); 151 } else if (shortest == fromTop) { 152 boundsOut.offsetTo(boundedLeft, movementBounds.top); 153 } else if (shortest == fromRight) { 154 boundsOut.offsetTo(movementBounds.right, boundedTop); 155 } else { 156 boundsOut.offsetTo(boundedLeft, movementBounds.bottom); 157 } 158 } 159 } 160