Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e1cb93f9 authored by Tony Wickham's avatar Tony Wickham Committed by Sunny Goyal
Browse files

Always place notification dots directly on adaptive icon path

- Calculate point on icon path nearest to top right corner, and
  use that as center for the dot
- Cleanup code related to dot offset

Test:
Set each style (different icon shape) and verify dot is in correct
placement for each of:
- Folders
- Icons in folders
- Icons in all apps
- Icons on workspace

Bug: 124414511
Change-Id: I036ed3677e8af222f00d4fad4a36a7e4d9b49ad9
parent 227b6e02
Loading
Loading
Loading
Loading
+48 −25
Original line number Diff line number Diff line
@@ -23,8 +23,10 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import android.view.ViewDebug;

@@ -36,33 +38,51 @@ public class DotRenderer {
    private static final String TAG = "DotRenderer";

    // The dot size is defined as a percentage of the app icon size.
    private static final float SIZE_PERCENTAGE = 0.38f;
    private static final float SIZE_PERCENTAGE = 0.228f;

    // Extra scale down of the dot
    private static final float DOT_SCALE = 0.6f;

    // Offset the dot slightly away from the icon if there's space.
    private static final float OFFSET_PERCENTAGE = 0.02f;

    private final float mDotCenterOffset;
    private final int mOffset;
    private final float mCircleRadius;
    private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);

    private final Bitmap mBackgroundWithShadow;
    private final float mBitmapOffset;

    public DotRenderer(int iconSizePx) {
        mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx;
        mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx);
    // Stores the center x and y position as a percentage (0 to 1) of the icon size
    private final float[] mRightDotPosition;
    private final float[] mLeftDotPosition;

        int size = (int) (DOT_SCALE * mDotCenterOffset);
    public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) {
        int size = Math.round(SIZE_PERCENTAGE * iconSizePx);
        ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
        builder.ambientShadowAlpha = 88;
        mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
        mCircleRadius = builder.radius;

        mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.

        // Find the points on the path that are closest to the top left and right corners.
        mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1);
        mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1);
    }

    private static float[] getPathPoint(Path path, float size, float direction) {
        float halfSize = size / 2;
        // Small delta so that we don't get a zero size triangle
        float delta = 1;

        float x = halfSize + direction * halfSize;
        Path trianglePath = new Path();
        trianglePath.moveTo(halfSize, halfSize);
        trianglePath.lineTo(x + delta * direction, 0);
        trianglePath.lineTo(x, -delta);
        trianglePath.close();

        trianglePath.op(path, Path.Op.INTERSECT);
        float[] pos = new float[2];
        new PathMeasure(trianglePath, false).getPosTan(0, pos, null);

        pos[0] = pos[0] / size;
        pos[1] = pos[1] / size;
        return pos;
    }

    /**
@@ -74,15 +94,21 @@ public class DotRenderer {
            return;
        }
        canvas.save();

        Rect iconBounds = params.iconBounds;
        float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition;
        float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0];
        float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1];

        // Ensure dot fits entirely in canvas clip bounds.
        Rect canvasBounds = canvas.getClipBounds();
        float offsetX = params.leftAlign
                ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset))
                : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset));
        float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset));

        // We draw the dot relative to its center.
        float dotCenterX = params.leftAlign
                ? params.iconBounds.left + mDotCenterOffset / 2
                : params.iconBounds.right - mDotCenterOffset / 2;
        float dotCenterY = params.iconBounds.top + mDotCenterOffset / 2;

        int offsetX = Math.min(mOffset, params.spaceForOffset.x);
        int offsetY = Math.min(mOffset, params.spaceForOffset.y);
        canvas.translate(dotCenterX + offsetX, dotCenterY - offsetY);
        canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY);
        canvas.scale(params.scale, params.scale);

        mCirclePaint.setColor(Color.BLACK);
@@ -102,9 +128,6 @@ public class DotRenderer {
        /** The progress of the animation, from 0 to 1. */
        @ViewDebug.ExportedProperty(category = "notification dot")
        public float scale;
        /** Overrides internally calculated offset if specified value is smaller. */
        @ViewDebug.ExportedProperty(category = "notification dot")
        public Point spaceForOffset = new Point();
        /** Whether the dot should align to the top left of the icon rather than the top right. */
        @ViewDebug.ExportedProperty(category = "notification dot")
        public boolean leftAlign;
+3 −1
Original line number Diff line number Diff line
@@ -46,10 +46,12 @@ import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconCache.IconLoadRequest;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.views.ActivityContext;

@@ -388,7 +390,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
    protected void drawDotIfNecessary(Canvas canvas) {
        if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
            getIconBounds(mDotParams.iconBounds);
            mDotParams.spaceForOffset.set((getWidth() - mIconSize) / 2, getPaddingTop());
            Utilities.scaleRectAboutCenter(mDotParams.iconBounds, IconShape.getNormalizationScale());
            final int scrollX = getScrollX();
            final int scrollY = getScrollY();
            canvas.translate(scrollX, scrollY);
+3 −4
Original line number Diff line number Diff line
@@ -24,15 +24,13 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;

import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;

import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;

public class DeviceProfile {

    public final InvariantDeviceProfile inv;
@@ -240,7 +238,8 @@ public class DeviceProfile {
        updateWorkspacePadding();

        // This is done last, after iconSizePx is calculated above.
        mDotRenderer = new DotRenderer(iconSizePx);
        mDotRenderer = new DotRenderer(iconSizePx, IconShape.getShapePath(),
                IconShape.DEFAULT_PATH_SIZE);
    }

    public DeviceProfile copy(Context context) {
+5 −4
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;

import com.android.launcher3.Alarm;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
@@ -50,11 +52,11 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.SimpleOnStylusPressListener;
import com.android.launcher3.StylusEventHelper;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.BaseItemDragListener;
@@ -68,8 +70,6 @@ import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.util.ArrayList;
import java.util.List;

import androidx.annotation.NonNull;

/**
 * An icon that can appear on in the workspace representing an {@link Folder}.
 */
@@ -510,10 +510,11 @@ public class FolderIcon extends FrameLayout implements FolderListener {
            Rect iconBounds = mDotParams.iconBounds;
            BubbleTextView.getIconBounds(this, iconBounds,
                    mLauncher.getWallpaperDeviceProfile().iconSizePx);
            float iconScale = (float) mBackground.previewSize / iconBounds.width();
            Utilities.scaleRectAboutCenter(iconBounds, iconScale);

            // If we are animating to the accepting state, animate the dot out.
            mDotParams.scale = Math.max(0, mDotScale - mBackground.getScaleProgress());
            mDotParams.spaceForOffset.set(getWidth() - iconBounds.right, iconBounds.top);
            mDotParams.color = mBackground.getDotColor();
            mDotRenderer.draw(canvas, mDotParams);
        }