From 45f2306ce96246b5ceb1d7723f1768e73aaa990f Mon Sep 17 00:00:00 2001 From: Thales Lima Date: Fri, 12 Nov 2021 11:20:09 +0000 Subject: [PATCH] open grid task menu to the right of icon This makes the TaskMenuViewWithArrow open to the right of the icon if there is enough space, and to the left if not. It also works correctly in RTL, inverting the side to open by default. Bug: 193432925 Test: open Overview and tap the app icon Change-Id: Ib1098f48ed28d2e0758fb0e3fb733e86271fa1a0 --- quickstep/res/values/dimens.xml | 1 + quickstep/src/com/android/quickstep/KtR.java | 1 + .../quickstep/views/TaskMenuViewWithArrow.kt | 123 +++++++++++++++++- .../android/launcher3/popup/ArrowPopup.java | 51 +++++--- .../launcher3/popup/RoundedArrowDrawable.java | 26 ++++ 5 files changed, 177 insertions(+), 25 deletions(-) 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);