Merge "Convert IconShape to Kotlin" into main
diff --git a/src/com/android/launcher3/graphics/IconShape.java b/src/com/android/launcher3/graphics/IconShape.java
deleted file mode 100644
index cb14587..0000000
--- a/src/com/android/launcher3/graphics/IconShape.java
+++ /dev/null
@@ -1,482 +0,0 @@
-/*
- * Copyright (C) 2018 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.launcher3.graphics;
-
-import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.FloatArrayEvaluator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.Region.Op;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.util.AttributeSet;
-import android.util.Xml;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-
-import com.android.launcher3.R;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.dagger.ApplicationContext;
-import com.android.launcher3.dagger.LauncherAppSingleton;
-import com.android.launcher3.dagger.LauncherBaseAppComponent;
-import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.icons.IconNormalizer;
-import com.android.launcher3.util.DaggerSingletonObject;
-import com.android.launcher3.views.ClipPathView;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Abstract representation of the shape of an icon shape
- */
-@LauncherAppSingleton
-public final class IconShape {
-
- public static DaggerSingletonObject<IconShape> INSTANCE =
- new DaggerSingletonObject<>(LauncherBaseAppComponent::getIconShape);
-
- private ShapeDelegate mDelegate = new Circle();
- private float mNormalizationScale = ICON_VISIBLE_AREA_FACTOR;
-
- @Inject
- public IconShape(@ApplicationContext Context context) {
- pickBestShape(context);
- }
-
- public ShapeDelegate getShape() {
- return mDelegate;
- }
-
- public float getNormalizationScale() {
- return mNormalizationScale;
- }
-
- /**
- * Initializes the shape which is closest to the {@link AdaptiveIconDrawable}
- */
- public void pickBestShape(Context context) {
- // Pick any large size
- final int size = 200;
-
- Region full = new Region(0, 0, size, size);
- Region iconR = new Region();
- AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
- new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
- drawable.setBounds(0, 0, size, size);
- iconR.setPath(drawable.getIconMask(), full);
-
- Path shapePath = new Path();
- Region shapeR = new Region();
-
- // Find the shape with minimum area of divergent region.
- int minArea = Integer.MAX_VALUE;
- ShapeDelegate closestShape = null;
- for (ShapeDelegate shape : getAllShapes(context)) {
- shapePath.reset();
- shape.addToPath(shapePath, 0, 0, size / 2f);
- shapeR.setPath(shapePath, full);
- shapeR.op(iconR, Op.XOR);
-
- int area = GraphicsUtils.getArea(shapeR);
- if (area < minArea) {
- minArea = area;
- closestShape = shape;
- }
- }
-
- if (closestShape != null) {
- mDelegate = closestShape;
- }
-
- // Initialize shape properties
- mNormalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, size, null);
- }
-
-
-
- public interface ShapeDelegate {
-
- default boolean enableShapeDetection() {
- return false;
- }
-
- void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint paint);
-
- void addToPath(Path path, float offsetX, float offsetY, float radius);
-
- <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
- Rect startRect, Rect endRect, float endRadius, boolean isReversed);
- }
-
- /**
- * Abstract shape where the reveal animation is a derivative of a round rect animation
- */
- private static abstract class SimpleRectShape implements ShapeDelegate {
-
- @Override
- public final <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
- Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
- return new RoundedRectRevealOutlineProvider(
- getStartRadius(startRect), endRadius, startRect, endRect) {
- @Override
- public boolean shouldRemoveElevationDuringAnimation() {
- return true;
- }
- }.createRevealAnimator(target, isReversed);
- }
-
- protected abstract float getStartRadius(Rect startRect);
- }
-
- /**
- * Abstract shape which draws using {@link Path}
- */
- private static abstract class PathShape implements ShapeDelegate {
-
- private final Path mTmpPath = new Path();
-
- @Override
- public final void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
- Paint paint) {
- mTmpPath.reset();
- addToPath(mTmpPath, offsetX, offsetY, radius);
- canvas.drawPath(mTmpPath, paint);
- }
-
- protected abstract AnimatorUpdateListener newUpdateListener(
- Rect startRect, Rect endRect, float endRadius, Path outPath);
-
- @Override
- public final <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
- Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
- Path path = new Path();
- AnimatorUpdateListener listener =
- newUpdateListener(startRect, endRect, endRadius, path);
-
- ValueAnimator va =
- isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
- va.addListener(new AnimatorListenerAdapter() {
- private ViewOutlineProvider mOldOutlineProvider;
-
- public void onAnimationStart(Animator animation) {
- mOldOutlineProvider = target.getOutlineProvider();
- target.setOutlineProvider(null);
-
- target.setTranslationZ(-target.getElevation());
- }
-
- public void onAnimationEnd(Animator animation) {
- target.setTranslationZ(0);
- target.setClipPath(null);
- target.setOutlineProvider(mOldOutlineProvider);
- }
- });
-
- va.addUpdateListener((anim) -> {
- path.reset();
- listener.onAnimationUpdate(anim);
- target.setClipPath(path);
- });
-
- return va;
- }
- }
-
- public static final class Circle extends PathShape {
-
- private final float[] mTempRadii = new float[8];
-
- protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
- float endRadius, Path outPath) {
- float r1 = getStartRadius(startRect);
-
- float[] startValues = new float[] {
- startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r1};
- float[] endValues = new float[] {
- endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius};
-
- FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]);
-
- return (anim) -> {
- float progress = (Float) anim.getAnimatedValue();
- float[] values = evaluator.evaluate(progress, startValues, endValues);
- outPath.addRoundRect(
- values[0], values[1], values[2], values[3],
- getRadiiArray(values[4], values[5]), Path.Direction.CW);
- };
- }
-
- private float[] getRadiiArray(float r1, float r2) {
- mTempRadii[0] = mTempRadii [1] = mTempRadii[2] = mTempRadii[3] =
- mTempRadii[6] = mTempRadii[7] = r1;
- mTempRadii[4] = mTempRadii[5] = r2;
- return mTempRadii;
- }
-
-
- @Override
- public void addToPath(Path path, float offsetX, float offsetY, float radius) {
- path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW);
- }
-
- protected float getStartRadius(Rect startRect) {
- return startRect.width() / 2f;
- }
-
- @Override
- public boolean enableShapeDetection() {
- return true;
- }
- }
-
- private static class RoundedSquare extends SimpleRectShape {
-
- /**
- * Ratio of corner radius to half size.
- */
- private final float mRadiusRatio;
-
- public RoundedSquare(float radiusRatio) {
- mRadiusRatio = radiusRatio;
- }
-
- @Override
- public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
- float cx = radius + offsetX;
- float cy = radius + offsetY;
- float cr = radius * mRadiusRatio;
- canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, p);
- }
-
- @Override
- public void addToPath(Path path, float offsetX, float offsetY, float radius) {
- float cx = radius + offsetX;
- float cy = radius + offsetY;
- float cr = radius * mRadiusRatio;
- path.addRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr,
- Path.Direction.CW);
- }
-
- @Override
- protected float getStartRadius(Rect startRect) {
- return (startRect.width() / 2f) * mRadiusRatio;
- }
- }
-
- private static class TearDrop extends PathShape {
-
- /**
- * Radio of short radius to large radius, based on the shape options defined in the config.
- */
- private final float mRadiusRatio;
- private final float[] mTempRadii = new float[8];
-
- public TearDrop(float radiusRatio) {
- mRadiusRatio = radiusRatio;
- }
-
- @Override
- public void addToPath(Path p, float offsetX, float offsetY, float r1) {
- float r2 = r1 * mRadiusRatio;
- float cx = r1 + offsetX;
- float cy = r1 + offsetY;
-
- p.addRoundRect(cx - r1, cy - r1, cx + r1, cy + r1, getRadiiArray(r1, r2),
- Path.Direction.CW);
- }
-
- private float[] getRadiiArray(float r1, float r2) {
- mTempRadii[0] = mTempRadii [1] = mTempRadii[2] = mTempRadii[3] =
- mTempRadii[6] = mTempRadii[7] = r1;
- mTempRadii[4] = mTempRadii[5] = r2;
- return mTempRadii;
- }
-
- @Override
- protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
- float endRadius, Path outPath) {
- float r1 = startRect.width() / 2f;
- float r2 = r1 * mRadiusRatio;
-
- float[] startValues = new float[] {
- startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r2};
- float[] endValues = new float[] {
- endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius};
-
- FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]);
-
- return (anim) -> {
- float progress = (Float) anim.getAnimatedValue();
- float[] values = evaluator.evaluate(progress, startValues, endValues);
- outPath.addRoundRect(
- values[0], values[1], values[2], values[3],
- getRadiiArray(values[4], values[5]), Path.Direction.CW);
- };
- }
- }
-
- private static class Squircle extends PathShape {
-
- /**
- * Radio of radius to circle radius, based on the shape options defined in the config.
- */
- private final float mRadiusRatio;
-
- public Squircle(float radiusRatio) {
- mRadiusRatio = radiusRatio;
- }
-
- @Override
- public void addToPath(Path p, float offsetX, float offsetY, float r) {
- float cx = r + offsetX;
- float cy = r + offsetY;
- float control = r - r * mRadiusRatio;
-
- p.moveTo(cx, cy - r);
- addLeftCurve(cx, cy, r, control, p);
- addRightCurve(cx, cy, r, control, p);
- addLeftCurve(cx, cy, -r, -control, p);
- addRightCurve(cx, cy, -r, -control, p);
- p.close();
- }
-
- private void addLeftCurve(float cx, float cy, float r, float control, Path path) {
- path.cubicTo(
- cx - control, cy - r,
- cx - r, cy - control,
- cx - r, cy);
- }
-
- private void addRightCurve(float cx, float cy, float r, float control, Path path) {
- path.cubicTo(
- cx - r, cy + control,
- cx - control, cy + r,
- cx, cy + r);
- }
-
- @Override
- protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
- float endR, Path outPath) {
-
- float startCX = startRect.exactCenterX();
- float startCY = startRect.exactCenterY();
- float startR = startRect.width() / 2f;
- float startControl = startR - startR * mRadiusRatio;
- float startHShift = 0;
- float startVShift = 0;
-
- float endCX = endRect.exactCenterX();
- float endCY = endRect.exactCenterY();
- // Approximate corner circle using bezier curves
- // http://spencermortensen.com/articles/bezier-circle/
- float endControl = endR * 0.551915024494f;
- float endHShift = endRect.width() / 2f - endR;
- float endVShift = endRect.height() / 2f - endR;
-
- return (anim) -> {
- float progress = (Float) anim.getAnimatedValue();
-
- float cx = (1 - progress) * startCX + progress * endCX;
- float cy = (1 - progress) * startCY + progress * endCY;
- float r = (1 - progress) * startR + progress * endR;
- float control = (1 - progress) * startControl + progress * endControl;
- float hShift = (1 - progress) * startHShift + progress * endHShift;
- float vShift = (1 - progress) * startVShift + progress * endVShift;
-
- outPath.moveTo(cx, cy - vShift - r);
- outPath.rLineTo(-hShift, 0);
-
- addLeftCurve(cx - hShift, cy - vShift, r, control, outPath);
- outPath.rLineTo(0, vShift + vShift);
-
- addRightCurve(cx - hShift, cy + vShift, r, control, outPath);
- outPath.rLineTo(hShift + hShift, 0);
-
- addLeftCurve(cx + hShift, cy + vShift, -r, -control, outPath);
- outPath.rLineTo(0, -vShift - vShift);
-
- addRightCurve(cx + hShift, cy - vShift, -r, -control, outPath);
- outPath.close();
- };
- }
- }
-
- private static ShapeDelegate getShapeDefinition(String type, float radius) {
- switch (type) {
- case "Circle":
- return new Circle();
- case "RoundedSquare":
- return new RoundedSquare(radius);
- case "TearDrop":
- return new TearDrop(radius);
- case "Squircle":
- return new Squircle(radius);
- default:
- throw new IllegalArgumentException("Invalid shape type: " + type);
- }
- }
-
- private static List<ShapeDelegate> getAllShapes(Context context) {
- ArrayList<ShapeDelegate> result = new ArrayList<>();
- try (XmlResourceParser parser = context.getResources().getXml(R.xml.folder_shapes)) {
-
- // Find the root tag
- int type;
- while ((type = parser.next()) != XmlPullParser.END_TAG
- && type != XmlPullParser.END_DOCUMENT
- && !"shapes".equals(parser.getName()));
-
- final int depth = parser.getDepth();
- int[] radiusAttr = new int[] {R.attr.folderIconRadius};
-
- while (((type = parser.next()) != XmlPullParser.END_TAG ||
- parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
-
- if (type == XmlPullParser.START_TAG) {
- AttributeSet attrs = Xml.asAttributeSet(parser);
- TypedArray a = context.obtainStyledAttributes(attrs, radiusAttr);
- ShapeDelegate shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1));
- a.recycle();
-
- result.add(shape);
- }
- }
- } catch (IOException | XmlPullParserException e) {
- throw new RuntimeException(e);
- }
- return result;
- }
-
-}
diff --git a/src/com/android/launcher3/graphics/IconShape.kt b/src/com/android/launcher3/graphics/IconShape.kt
new file mode 100644
index 0000000..22d3f3d
--- /dev/null
+++ b/src/com/android/launcher3/graphics/IconShape.kt
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2018 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.launcher3.graphics
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.FloatArrayEvaluator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.Region
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.ColorDrawable
+import android.util.Xml
+import android.view.View
+import android.view.ViewOutlineProvider
+import com.android.launcher3.R
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.icons.GraphicsUtils
+import com.android.launcher3.icons.IconNormalizer
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.views.ClipPathView
+import java.io.IOException
+import javax.inject.Inject
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+
+/** Abstract representation of the shape of an icon shape */
+@LauncherAppSingleton
+class IconShape @Inject constructor(@ApplicationContext context: Context) {
+ var shape: ShapeDelegate = Circle()
+ private set
+
+ var normalizationScale: Float = IconNormalizer.ICON_VISIBLE_AREA_FACTOR
+ private set
+
+ init {
+ pickBestShape(context)
+ }
+
+ /** Initializes the shape which is closest to the [AdaptiveIconDrawable] */
+ fun pickBestShape(context: Context) {
+ // Pick any large size
+ val size = 200
+ val full = Region(0, 0, size, size)
+ val shapePath = Path()
+ val shapeR = Region()
+ val iconR = Region()
+ val drawable = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), ColorDrawable(Color.BLACK))
+ drawable.setBounds(0, 0, size, size)
+ iconR.setPath(drawable.iconMask, full)
+
+ // Find the shape with minimum area of divergent region.
+ var minArea = Int.MAX_VALUE
+ var closestShape: ShapeDelegate? = null
+ for (shape in getAllShapes(context)) {
+ shapePath.reset()
+ shape.addToPath(shapePath, 0f, 0f, size / 2f)
+ shapeR.setPath(shapePath, full)
+ shapeR.op(iconR, Region.Op.XOR)
+
+ val area = GraphicsUtils.getArea(shapeR)
+ if (area < minArea) {
+ minArea = area
+ closestShape = shape
+ }
+ }
+
+ if (closestShape != null) {
+ shape = closestShape
+ }
+
+ // Initialize shape properties
+ normalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, size, null)
+ }
+
+ interface ShapeDelegate {
+ fun enableShapeDetection(): Boolean {
+ return false
+ }
+
+ fun drawShape(canvas: Canvas, offsetX: Float, offsetY: Float, radius: Float, paint: Paint)
+
+ fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float)
+
+ fun <T> createRevealAnimator(
+ target: T,
+ startRect: Rect,
+ endRect: Rect,
+ endRadius: Float,
+ isReversed: Boolean,
+ ): ValueAnimator where T : View?, T : ClipPathView?
+ }
+
+ /** Abstract shape where the reveal animation is a derivative of a round rect animation */
+ private abstract class SimpleRectShape : ShapeDelegate {
+ override fun <T> createRevealAnimator(
+ target: T,
+ startRect: Rect,
+ endRect: Rect,
+ endRadius: Float,
+ isReversed: Boolean,
+ ): ValueAnimator where T : View?, T : ClipPathView? {
+ return object :
+ RoundedRectRevealOutlineProvider(
+ getStartRadius(startRect),
+ endRadius,
+ startRect,
+ endRect,
+ ) {
+ override fun shouldRemoveElevationDuringAnimation(): Boolean {
+ return true
+ }
+ }
+ .createRevealAnimator(target, isReversed)
+ }
+
+ protected abstract fun getStartRadius(startRect: Rect): Float
+ }
+
+ /** Abstract shape which draws using [Path] */
+ abstract class PathShape : ShapeDelegate {
+ private val mTmpPath = Path()
+
+ override fun drawShape(
+ canvas: Canvas,
+ offsetX: Float,
+ offsetY: Float,
+ radius: Float,
+ paint: Paint,
+ ) {
+ mTmpPath.reset()
+ addToPath(mTmpPath, offsetX, offsetY, radius)
+ canvas.drawPath(mTmpPath, paint)
+ }
+
+ protected abstract fun newUpdateListener(
+ startRect: Rect,
+ endRect: Rect,
+ endRadius: Float,
+ outPath: Path,
+ ): ValueAnimator.AnimatorUpdateListener
+
+ override fun <T> createRevealAnimator(
+ target: T,
+ startRect: Rect,
+ endRect: Rect,
+ endRadius: Float,
+ isReversed: Boolean,
+ ): ValueAnimator where T : View?, T : ClipPathView? {
+ val path = Path()
+ val listener = newUpdateListener(startRect, endRect, endRadius, path)
+
+ val va =
+ if (isReversed) ValueAnimator.ofFloat(1f, 0f) else ValueAnimator.ofFloat(0f, 1f)
+ va.addListener(
+ object : AnimatorListenerAdapter() {
+ private var mOldOutlineProvider: ViewOutlineProvider? = null
+
+ override fun onAnimationStart(animation: Animator) {
+ target?.apply {
+ mOldOutlineProvider = outlineProvider
+ outlineProvider = null
+ translationZ = -target.elevation
+ }
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ target?.apply {
+ translationZ = 0f
+ setClipPath(null)
+ outlineProvider = mOldOutlineProvider
+ }
+ }
+ }
+ )
+
+ va.addUpdateListener { anim: ValueAnimator ->
+ path.reset()
+ listener.onAnimationUpdate(anim)
+ target?.setClipPath(path)
+ }
+
+ return va
+ }
+ }
+
+ open class Circle : PathShape() {
+ private val mTempRadii = FloatArray(8)
+
+ override fun newUpdateListener(
+ startRect: Rect,
+ endRect: Rect,
+ endRadius: Float,
+ outPath: Path,
+ ): ValueAnimator.AnimatorUpdateListener {
+ val r1 = getStartRadius(startRect)
+
+ val startValues =
+ floatArrayOf(
+ startRect.left.toFloat(),
+ startRect.top.toFloat(),
+ startRect.right.toFloat(),
+ startRect.bottom.toFloat(),
+ r1,
+ r1,
+ )
+ val endValues =
+ floatArrayOf(
+ endRect.left.toFloat(),
+ endRect.top.toFloat(),
+ endRect.right.toFloat(),
+ endRect.bottom.toFloat(),
+ endRadius,
+ endRadius,
+ )
+
+ val evaluator = FloatArrayEvaluator(FloatArray(6))
+
+ return ValueAnimator.AnimatorUpdateListener { anim: ValueAnimator ->
+ val progress = anim.animatedValue as Float
+ val values = evaluator.evaluate(progress, startValues, endValues)
+ outPath.addRoundRect(
+ values[0],
+ values[1],
+ values[2],
+ values[3],
+ getRadiiArray(values[4], values[5]),
+ Path.Direction.CW,
+ )
+ }
+ }
+
+ private fun getRadiiArray(r1: Float, r2: Float): FloatArray {
+ mTempRadii[7] = r1
+ mTempRadii[6] = mTempRadii[7]
+ mTempRadii[3] = mTempRadii[6]
+ mTempRadii[2] = mTempRadii[3]
+ mTempRadii[1] = mTempRadii[2]
+ mTempRadii[0] = mTempRadii[1]
+ mTempRadii[5] = r2
+ mTempRadii[4] = mTempRadii[5]
+ return mTempRadii
+ }
+
+ override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) {
+ path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW)
+ }
+
+ private fun getStartRadius(startRect: Rect): Float {
+ return startRect.width() / 2f
+ }
+
+ override fun enableShapeDetection(): Boolean {
+ return true
+ }
+ }
+
+ private class RoundedSquare(
+ /** Ratio of corner radius to half size. */
+ private val mRadiusRatio: Float
+ ) : SimpleRectShape() {
+ override fun drawShape(
+ canvas: Canvas,
+ offsetX: Float,
+ offsetY: Float,
+ radius: Float,
+ paint: Paint,
+ ) {
+ val cx = radius + offsetX
+ val cy = radius + offsetY
+ val cr = radius * mRadiusRatio
+ canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, paint)
+ }
+
+ override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) {
+ val cx = radius + offsetX
+ val cy = radius + offsetY
+ val cr = radius * mRadiusRatio
+ path.addRoundRect(
+ cx - radius,
+ cy - radius,
+ cx + radius,
+ cy + radius,
+ cr,
+ cr,
+ Path.Direction.CW,
+ )
+ }
+
+ override fun getStartRadius(startRect: Rect): Float {
+ return (startRect.width() / 2f) * mRadiusRatio
+ }
+ }
+
+ private class TearDrop(
+ /**
+ * Radio of short radius to large radius, based on the shape options defined in the config.
+ */
+ private val mRadiusRatio: Float
+ ) : PathShape() {
+ private val mTempRadii = FloatArray(8)
+
+ override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) {
+ val r2 = radius * mRadiusRatio
+ val cx = radius + offsetX
+ val cy = radius + offsetY
+
+ path.addRoundRect(
+ cx - radius,
+ cy - radius,
+ cx + radius,
+ cy + radius,
+ getRadiiArray(radius, r2),
+ Path.Direction.CW,
+ )
+ }
+
+ fun getRadiiArray(r1: Float, r2: Float): FloatArray {
+ mTempRadii[7] = r1
+ mTempRadii[6] = mTempRadii[7]
+ mTempRadii[3] = mTempRadii[6]
+ mTempRadii[2] = mTempRadii[3]
+ mTempRadii[1] = mTempRadii[2]
+ mTempRadii[0] = mTempRadii[1]
+ mTempRadii[5] = r2
+ mTempRadii[4] = mTempRadii[5]
+ return mTempRadii
+ }
+
+ override fun newUpdateListener(
+ startRect: Rect,
+ endRect: Rect,
+ endRadius: Float,
+ outPath: Path,
+ ): ValueAnimator.AnimatorUpdateListener {
+ val r1 = startRect.width() / 2f
+ val r2 = r1 * mRadiusRatio
+
+ val startValues =
+ floatArrayOf(
+ startRect.left.toFloat(),
+ startRect.top.toFloat(),
+ startRect.right.toFloat(),
+ startRect.bottom.toFloat(),
+ r1,
+ r2,
+ )
+ val endValues =
+ floatArrayOf(
+ endRect.left.toFloat(),
+ endRect.top.toFloat(),
+ endRect.right.toFloat(),
+ endRect.bottom.toFloat(),
+ endRadius,
+ endRadius,
+ )
+
+ val evaluator = FloatArrayEvaluator(FloatArray(6))
+
+ return ValueAnimator.AnimatorUpdateListener { anim: ValueAnimator ->
+ val progress = anim.animatedValue as Float
+ val values = evaluator.evaluate(progress, startValues, endValues)
+ outPath.addRoundRect(
+ values[0],
+ values[1],
+ values[2],
+ values[3],
+ getRadiiArray(values[4], values[5]),
+ Path.Direction.CW,
+ )
+ }
+ }
+ }
+
+ private class Squircle(
+ /** Radio of radius to circle radius, based on the shape options defined in the config. */
+ private val mRadiusRatio: Float
+ ) : PathShape() {
+ override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) {
+ val cx = radius + offsetX
+ val cy = radius + offsetY
+ val control = radius - radius * mRadiusRatio
+
+ path.moveTo(cx, cy - radius)
+ addLeftCurve(cx, cy, radius, control, path)
+ addRightCurve(cx, cy, radius, control, path)
+ addLeftCurve(cx, cy, -radius, -control, path)
+ addRightCurve(cx, cy, -radius, -control, path)
+ path.close()
+ }
+
+ fun addLeftCurve(cx: Float, cy: Float, r: Float, control: Float, path: Path) {
+ path.cubicTo(cx - control, cy - r, cx - r, cy - control, cx - r, cy)
+ }
+
+ fun addRightCurve(cx: Float, cy: Float, r: Float, control: Float, path: Path) {
+ path.cubicTo(cx - r, cy + control, cx - control, cy + r, cx, cy + r)
+ }
+
+ override fun newUpdateListener(
+ startRect: Rect,
+ endRect: Rect,
+ endRadius: Float,
+ outPath: Path,
+ ): ValueAnimator.AnimatorUpdateListener {
+ val startCX = startRect.exactCenterX()
+ val startCY = startRect.exactCenterY()
+ val startR = startRect.width() / 2f
+ val startControl = startR - startR * mRadiusRatio
+ val startHShift = 0f
+ val startVShift = 0f
+
+ val endCX = endRect.exactCenterX()
+ val endCY = endRect.exactCenterY()
+ // Approximate corner circle using bezier curves
+ // http://spencermortensen.com/articles/bezier-circle/
+ val endControl = endRadius * 0.551915024494f
+ val endHShift = endRect.width() / 2f - endRadius
+ val endVShift = endRect.height() / 2f - endRadius
+
+ return ValueAnimator.AnimatorUpdateListener { anim: ValueAnimator ->
+ val progress = anim.animatedValue as Float
+ val cx = (1 - progress) * startCX + progress * endCX
+ val cy = (1 - progress) * startCY + progress * endCY
+ val r = (1 - progress) * startR + progress * endRadius
+ val control = (1 - progress) * startControl + progress * endControl
+ val hShift = (1 - progress) * startHShift + progress * endHShift
+ val vShift = (1 - progress) * startVShift + progress * endVShift
+
+ outPath.moveTo(cx, cy - vShift - r)
+ outPath.rLineTo(-hShift, 0f)
+
+ addLeftCurve(cx - hShift, cy - vShift, r, control, outPath)
+ outPath.rLineTo(0f, vShift + vShift)
+
+ addRightCurve(cx - hShift, cy + vShift, r, control, outPath)
+ outPath.rLineTo(hShift + hShift, 0f)
+
+ addLeftCurve(cx + hShift, cy + vShift, -r, -control, outPath)
+ outPath.rLineTo(0f, -vShift - vShift)
+
+ addRightCurve(cx + hShift, cy - vShift, -r, -control, outPath)
+ outPath.close()
+ }
+ }
+ }
+
+ companion object {
+ @JvmField var INSTANCE = DaggerSingletonObject(LauncherAppComponent::getIconShape)
+
+ private fun getShapeDefinition(type: String, radius: Float): ShapeDelegate {
+ return when (type) {
+ "Circle" -> Circle()
+ "RoundedSquare" -> RoundedSquare(radius)
+ "TearDrop" -> TearDrop(radius)
+ "Squircle" -> Squircle(radius)
+ else -> throw IllegalArgumentException("Invalid shape type: $type")
+ }
+ }
+
+ private fun getAllShapes(context: Context): List<ShapeDelegate> {
+ val result = ArrayList<ShapeDelegate>()
+ try {
+ context.resources.getXml(R.xml.folder_shapes).use { parser ->
+ // Find the root tag
+ var type: Int = parser.next()
+ while (
+ type != XmlPullParser.END_TAG &&
+ type != XmlPullParser.END_DOCUMENT &&
+ "shapes" != parser.name
+ ) {
+ type = parser.next()
+ }
+ val depth = parser.depth
+ val radiusAttr = intArrayOf(R.attr.folderIconRadius)
+ type = parser.next()
+ while (
+ (type != XmlPullParser.END_TAG || parser.depth > depth) &&
+ type != XmlPullParser.END_DOCUMENT
+ ) {
+ if (type == XmlPullParser.START_TAG) {
+ val attrs = Xml.asAttributeSet(parser)
+ val arr = context.obtainStyledAttributes(attrs, radiusAttr)
+ val shape = getShapeDefinition(parser.name, arr.getFloat(0, 1f))
+ arr.recycle()
+ result.add(shape)
+ }
+ type = parser.next()
+ }
+ }
+ } catch (e: IOException) {
+ throw RuntimeException(e)
+ } catch (e: XmlPullParserException) {
+ throw RuntimeException(e)
+ }
+ return result
+ }
+ }
+}