diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index e48c9e8b6b..4e6b7b99df 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -28,6 +28,7 @@ 4dp 2dp 234dp + 8dp 48dp 16dp diff --git a/quickstep/src/com/android/quickstep/KtR.java b/quickstep/src/com/android/quickstep/KtR.java index 57dad08456..a768ef5253 100644 --- a/quickstep/src/com/android/quickstep/KtR.java +++ b/quickstep/src/com/android/quickstep/KtR.java @@ -30,6 +30,7 @@ public class KtR { public static final class dimen { public static int task_menu_spacing = R.dimen.task_menu_spacing; + public static int task_menu_horizontal_padding = R.dimen.task_menu_horizontal_padding; } public static final class layout { diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt index 5059f8b532..179fd68361 100644 --- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt +++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt @@ -23,14 +23,18 @@ import android.graphics.Rect import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.shapes.RectShape import android.util.AttributeSet +import android.view.Gravity import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import android.widget.LinearLayout import com.android.launcher3.BaseDraggingActivity import com.android.launcher3.DeviceProfile +import com.android.launcher3.InsettableFrameLayout import com.android.launcher3.R import com.android.launcher3.popup.ArrowPopup +import com.android.launcher3.popup.RoundedArrowDrawable import com.android.launcher3.popup.SystemShortcut import com.android.launcher3.util.Themes import com.android.quickstep.KtR @@ -43,9 +47,13 @@ class TaskMenuViewWithArrow : ArrowPopup { fun showForTask(taskContainer: TaskIdAttributeContainer): Boolean { val activity = BaseDraggingActivity - .fromContext(taskContainer.taskView.context) + .fromContext(taskContainer.taskView.context) val taskMenuViewWithArrow = activity.layoutInflater - .inflate(KtR.layout.task_menu_with_arrow, activity.dragLayer, false) as TaskMenuViewWithArrow<*> + .inflate( + KtR.layout.task_menu_with_arrow, + activity.dragLayer, + false + ) as TaskMenuViewWithArrow<*> return taskMenuViewWithArrow.populateAndShowForTask(taskContainer) } @@ -53,10 +61,21 @@ class TaskMenuViewWithArrow : ArrowPopup { constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) init { clipToOutline = true + + shouldScaleArrow = true + // This synchronizes the arrow and menu to open at the same time + OPEN_CHILD_FADE_START_DELAY = OPEN_FADE_START_DELAY + OPEN_CHILD_FADE_DURATION = OPEN_FADE_DURATION + CLOSE_FADE_START_DELAY = CLOSE_CHILD_FADE_START_DELAY + CLOSE_FADE_DURATION = CLOSE_CHILD_FADE_DURATION } private val menuWidth = context.resources.getDimensionPixelSize(R.dimen.task_menu_width_grid) @@ -65,6 +84,13 @@ class TaskMenuViewWithArrow : ArrowPopup { private lateinit var optionLayout: LinearLayout private lateinit var taskContainer: TaskIdAttributeContainer + private var optionMeasuredHeight = 0 + private val arrowHorizontalPadding: Int + get() = if (taskView.isFocusedTask) + resources.getDimensionPixelSize(KtR.dimen.task_menu_horizontal_padding) + else + 0 + override fun isOfType(type: Int): Boolean = type and TYPE_TASK_MENU != 0 override fun getTargetObjectLocation(outPos: Rect?) { @@ -147,7 +173,10 @@ class TaskMenuViewWithArrow : ArrowPopup { } override fun assignMarginsAndBackgrounds(viewGroup: ViewGroup) { - assignMarginsAndBackgrounds(this, Themes.getAttrColor(context, com.android.internal.R.attr.colorSurface)) + assignMarginsAndBackgrounds( + this, + Themes.getAttrColor(context, com.android.internal.R.attr.colorSurface) + ) } override fun onCreateOpenAnimation(anim: AnimatorSet) { @@ -164,4 +193,90 @@ class TaskMenuViewWithArrow : ArrowPopup { ObjectAnimator.ofFloat(taskContainer.thumbnailView, TaskThumbnailView.DIM_ALPHA, 0f) ) } + + /** + * Orients this container to the left or right of the given icon, aligning with the first option + * or second. + * + * These are the preferred orientations, in order (RTL prefers right-aligned over left): + * - Right and first option aligned + * - Right and second option aligned + * - Left and first option aligned + * - Left and second option aligned + * + * So we always align right if there is enough horizontal space + */ + override fun orientAboutObject() { + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) + // Needed for offsets later + optionMeasuredHeight = optionLayout.getChildAt(0).measuredHeight + val extraHorizontalSpace = (mArrowHeight + mArrowOffsetVertical + arrowHorizontalPadding) + + val widthWithArrow = measuredWidth + paddingLeft + paddingRight + extraHorizontalSpace + getTargetObjectLocation(mTempRect) + val dragLayer: InsettableFrameLayout = popupContainer + val insets = dragLayer.insets + + // Put to the right of the icon if there is space, which means left aligned with the menu + val rightAlignedMenuStartX = mTempRect.left - widthWithArrow + val leftAlignedMenuStartX = mTempRect.right + extraHorizontalSpace + mIsLeftAligned = if (mIsRtl) { + rightAlignedMenuStartX + insets.left < 0 + } else { + leftAlignedMenuStartX + (widthWithArrow - extraHorizontalSpace) + insets.left < + dragLayer.width - insets.right + } + + var menuStartX = if (mIsLeftAligned) leftAlignedMenuStartX else rightAlignedMenuStartX + + // Offset y so that the arrow and first row are center-aligned with the original icon. + val iconHeight = mTempRect.height() + val optionHeight = optionMeasuredHeight + val yOffset = (optionHeight - iconHeight) / 2 + var menuStartY = mTempRect.top - yOffset + + // Insets are added later, so subtract them now. + menuStartX -= insets.left + menuStartY -= insets.top + + setX(menuStartX.toFloat()) + setY(menuStartY.toFloat()) + + val lp = layoutParams as FrameLayout.LayoutParams + val arrowLp = mArrow.layoutParams as FrameLayout.LayoutParams + lp.gravity = Gravity.TOP + arrowLp.gravity = lp.gravity + } + + override fun addArrow() { + popupContainer.addView(mArrow) + mArrow.x = getArrowX() + mArrow.y = y + (optionMeasuredHeight / 2) - (mArrowHeight / 2) + + updateArrowColor() + + // This is inverted (x = height, y = width) because the arrow is rotated + mArrow.pivotX = if (mIsLeftAligned) 0f else mArrowHeight.toFloat() + mArrow.pivotY = 0f + } + + private fun getArrowX(): Float { + return if (mIsLeftAligned) + x - mArrowHeight + else + x + measuredWidth + mArrowOffsetVertical + } + + override fun updateArrowColor() { + mArrow.background = RoundedArrowDrawable( + mArrowWidth.toFloat(), + mArrowHeight.toFloat(), + mArrowPointRadius.toFloat(), + mIsLeftAligned, + mArrowColor + ) + elevation = mElevation + mArrow.elevation = mElevation + } + } \ No newline at end of file diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java index 5a1e4bf27e..b1a41093f5 100644 --- a/src/com/android/launcher3/popup/ArrowPopup.java +++ b/src/com/android/launcher3/popup/ArrowPopup.java @@ -77,44 +77,45 @@ public abstract class ArrowPopup extends AbstractFloatingView { // Duration values (ms) for popup open and close animations. - private static final int OPEN_DURATION = 276; - private static final int OPEN_FADE_START_DELAY = 0; - private static final int OPEN_FADE_DURATION = 38; - private static final int OPEN_CHILD_FADE_START_DELAY = 38; - private static final int OPEN_CHILD_FADE_DURATION = 76; + protected int OPEN_DURATION = 276; + protected int OPEN_FADE_START_DELAY = 0; + protected int OPEN_FADE_DURATION = 38; + protected int OPEN_CHILD_FADE_START_DELAY = 38; + protected int OPEN_CHILD_FADE_DURATION = 76; - private static final int CLOSE_DURATION = 200; - private static final int CLOSE_FADE_START_DELAY = 140; - private static final int CLOSE_FADE_DURATION = 50; - private static final int CLOSE_CHILD_FADE_START_DELAY = 0; - private static final int CLOSE_CHILD_FADE_DURATION = 140; + protected int CLOSE_DURATION = 200; + protected int CLOSE_FADE_START_DELAY = 140; + protected int CLOSE_FADE_DURATION = 50; + protected int CLOSE_CHILD_FADE_START_DELAY = 0; + protected int CLOSE_CHILD_FADE_DURATION = 140; // Index used to get background color when using local wallpaper color extraction, private static final int DARK_COLOR_EXTRACTION_INDEX = android.R.color.system_neutral2_800; private static final int LIGHT_COLOR_EXTRACTION_INDEX = android.R.color.system_accent2_50; - private final Rect mTempRect = new Rect(); + protected final Rect mTempRect = new Rect(); protected final LayoutInflater mInflater; - private final float mOutlineRadius; + protected final float mOutlineRadius; protected final T mActivityContext; protected final boolean mIsRtl; - private final int mArrowOffsetVertical; - private final int mArrowOffsetHorizontal; - private final int mArrowWidth; - private final int mArrowHeight; - private final int mArrowPointRadius; - private final View mArrow; + protected final int mArrowOffsetVertical; + protected final int mArrowOffsetHorizontal; + protected final int mArrowWidth; + protected final int mArrowHeight; + protected final int mArrowPointRadius; + protected final View mArrow; private final int mMargin; protected boolean mIsLeftAligned; protected boolean mIsAboveIcon; - private int mGravity; + protected int mGravity; protected AnimatorSet mOpenCloseAnimator; protected boolean mDeferContainerRemoval; + protected boolean shouldScaleArrow = false; private final GradientDrawable mRoundedTop; private final GradientDrawable mRoundedBottom; @@ -122,10 +123,10 @@ public abstract class ArrowPopup private Runnable mOnCloseCallback = () -> { }; // The rect string of the view that the arrow is attached to, in screen reference frame. - private int mArrowColor; + protected int mArrowColor; protected final List mColorExtractors; - private final float mElevation; + protected final float mElevation; private final int mBackgroundColor; private final String mIterateChildrenTag; @@ -729,6 +730,14 @@ public abstract class ArrowPopup scale.setInterpolator(interpolator); animatorSet.play(scale); + if (shouldScaleArrow) { + Animator arrowScaleAnimator = ObjectAnimator.ofFloat(mArrow, View.SCALE_Y, + scaleValues); + arrowScaleAnimator.setDuration(totalDuration); + arrowScaleAnimator.setInterpolator(interpolator); + animatorSet.play(arrowScaleAnimator); + } + fadeInChildViews(this, alphaValues, childFadeStartDelay, childFadeDuration, animatorSet); return animatorSet; diff --git a/src/com/android/launcher3/popup/RoundedArrowDrawable.java b/src/com/android/launcher3/popup/RoundedArrowDrawable.java index e662d5c039..436aa51de9 100644 --- a/src/com/android/launcher3/popup/RoundedArrowDrawable.java +++ b/src/com/android/launcher3/popup/RoundedArrowDrawable.java @@ -78,6 +78,32 @@ public class RoundedArrowDrawable extends Drawable { mPath.transform(pathTransform); } + /** + * Constructor for an arrow that points to the left or right. + * + * @param width of the arrow. + * @param height of the arrow. + * @param radius of the tip of the arrow. + * @param isPointingLeft or not. + * @param color to draw the triangle. + */ + public RoundedArrowDrawable(float width, float height, float radius, boolean isPointingLeft, + int color) { + mPath = new Path(); + mPaint = new Paint(); + mPaint.setColor(color); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setAntiAlias(true); + + // Make the drawable with the triangle pointing down... + addDownPointingRoundedTriangleToPath(width, height, radius, mPath); + + // ... then rotate it to the side it needs to point. + Matrix pathTransform = new Matrix(); + pathTransform.setRotate(isPointingLeft ? 90 : -90, width * 0.5f, height * 0.5f); + mPath.transform(pathTransform); + } + @Override public void draw(Canvas canvas) { canvas.drawPath(mPath, mPaint);