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
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index e48c9e8..4e6b7b9 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -28,6 +28,7 @@
<dimen name="task_menu_item_corner_radius">4dp</dimen>
<dimen name="task_menu_spacing">2dp</dimen>
<dimen name="task_menu_width_grid">234dp</dimen>
+ <dimen name="task_menu_horizontal_padding">8dp</dimen>
<dimen name="overview_proactive_row_height">48dp</dimen>
<dimen name="overview_proactive_row_bottom_margin">16dp</dimen>
diff --git a/quickstep/src/com/android/quickstep/KtR.java b/quickstep/src/com/android/quickstep/KtR.java
index 57dad08..a768ef5 100644
--- a/quickstep/src/com/android/quickstep/KtR.java
+++ b/quickstep/src/com/android/quickstep/KtR.java
@@ -30,6 +30,7 @@
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 5059f8b..179fd68 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.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 @@
fun showForTask(taskContainer: TaskIdAttributeContainer): Boolean {
val activity = BaseDraggingActivity
- .fromContext<BaseDraggingActivity>(taskContainer.taskView.context)
+ .fromContext<BaseDraggingActivity>(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 @@
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 @@
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 @@
}
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 @@
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 5a1e4bf..b1a4109 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -77,44 +77,45 @@
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 @@
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<LocalColorExtractor> mColorExtractors;
- private final float mElevation;
+ protected final float mElevation;
private final int mBackgroundColor;
private final String mIterateChildrenTag;
@@ -729,6 +730,14 @@
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 e662d5c..436aa51 100644
--- a/src/com/android/launcher3/popup/RoundedArrowDrawable.java
+++ b/src/com/android/launcher3/popup/RoundedArrowDrawable.java
@@ -78,6 +78,32 @@
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);