1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.allapps;
17 
18 import android.content.Context;
19 import android.graphics.Canvas;
20 import android.graphics.Paint;
21 import android.graphics.Path;
22 import android.graphics.RectF;
23 import android.graphics.drawable.GradientDrawable;
24 import android.graphics.drawable.InsetDrawable;
25 import android.view.View;
26 
27 import androidx.annotation.Nullable;
28 import androidx.core.content.ContextCompat;
29 
30 import com.android.launcher3.R;
31 import com.android.launcher3.util.Themes;
32 
33 public class SectionDecorationHandler {
34 
35     protected final Path mTmpPath = new Path();
36     protected final RectF mTmpRect = new RectF();
37 
38     protected final int mCornerGroupRadius;
39     protected final int mCornerResultRadius;
40     protected final RectF mBounds = new RectF();
41     protected final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
42 
43     protected final int mFocusAlpha = 255; // main focused item alpha
44     protected int mFillColor; // grouping color
45     protected int mFocusColor; // main focused item color
46     protected float mFillSpacing;
47     protected int mInlineRadius;
48     protected Context mContext;
49     protected float[] mCorners;
50     protected int mFillAlpha;
51     protected boolean mIsTopLeftRound;
52     protected boolean mIsTopRightRound;
53     protected boolean mIsBottomLeftRound;
54     protected boolean mIsBottomRightRound;
55     protected boolean mIsBottomRound;
56     protected boolean mIsTopRound;
57 
SectionDecorationHandler(Context context, int fillAlpha, boolean isTopLeftRound, boolean isTopRightRound, boolean isBottomLeftRound, boolean isBottomRightRound)58     public SectionDecorationHandler(Context context, int fillAlpha, boolean isTopLeftRound,
59             boolean isTopRightRound, boolean isBottomLeftRound,
60             boolean isBottomRightRound) {
61 
62         mContext = context;
63         mFillAlpha = fillAlpha;
64         mFocusColor = ContextCompat.getColor(context,
65                 R.color.material_color_surface_bright); // UX recommended
66         mFillColor = ContextCompat.getColor(context,
67                 R.color.material_color_surface_container_high); // UX recommended
68 
69         mIsTopLeftRound = isTopLeftRound;
70         mIsTopRightRound = isTopRightRound;
71         mIsBottomLeftRound = isBottomLeftRound;
72         mIsBottomRightRound = isBottomRightRound;
73         mIsBottomRound = mIsBottomLeftRound && mIsBottomRightRound;
74         mIsTopRound = mIsTopLeftRound && mIsTopRightRound;
75 
76         mCornerGroupRadius = context.getResources().getDimensionPixelSize(
77                 R.dimen.all_apps_recycler_view_decorator_group_radius);
78         mCornerResultRadius = context.getResources().getDimensionPixelSize(
79                 R.dimen.all_apps_recycler_view_decorator_result_radius);
80 
81         mInlineRadius = 0;
82         mFillSpacing = 0;
83         initCorners();
84     }
85 
initCorners()86     protected void initCorners() {
87         mCorners = new float[]{
88                 mIsTopLeftRound ? mCornerGroupRadius : 0,
89                 mIsTopLeftRound ? mCornerGroupRadius : 0, // Top left radius in px
90                 mIsTopRightRound ? mCornerGroupRadius : 0,
91                 mIsTopRightRound ? mCornerGroupRadius : 0, // Top right radius in px
92                 mIsBottomRightRound ? mCornerGroupRadius : 0,
93                 mIsBottomRightRound ? mCornerGroupRadius : 0, // Bottom right
94                 mIsBottomLeftRound ? mCornerGroupRadius : 0,
95                 mIsBottomLeftRound ? mCornerGroupRadius : 0 // Bottom left
96         };
97     }
98 
setFillAlpha(int fillAlpha)99     protected void setFillAlpha(int fillAlpha) {
100         mFillAlpha = fillAlpha;
101         mPaint.setAlpha(mFillAlpha);
102     }
103 
onFocusDraw(Canvas canvas, @Nullable View view)104     protected void onFocusDraw(Canvas canvas, @Nullable View view) {
105         if (view == null) {
106             return;
107         }
108         mPaint.setColor(mFillColor);
109         mPaint.setAlpha(mFillAlpha);
110         int scaledHeight = (int) (view.getHeight() * view.getScaleY());
111         mBounds.set(view.getLeft(), view.getY(), view.getRight(), view.getY() + scaledHeight);
112         onDraw(canvas);
113     }
114 
onDraw(Canvas canvas)115     protected void onDraw(Canvas canvas) {
116         mTmpPath.reset();
117         mTmpRect.set(mBounds.left + mFillSpacing,
118                 mBounds.top + mFillSpacing,
119                 mBounds.right - mFillSpacing,
120                 mBounds.bottom - mFillSpacing);
121         mTmpPath.addRoundRect(mTmpRect, mCorners, Path.Direction.CW);
122         canvas.drawPath(mTmpPath, mPaint);
123     }
124 
125     /** Sets the right background drawable to the view based on the give decoration info. */
applyBackground(View view, Context context, @Nullable SectionDecorationInfo decorationInfo, boolean isHighlighted)126     public void applyBackground(View view, Context context,
127            @Nullable SectionDecorationInfo decorationInfo, boolean isHighlighted) {
128         int inset = context.getResources().getDimensionPixelSize(
129                 R.dimen.all_apps_recycler_view_decorator_padding);
130         float radiusBottom = (decorationInfo == null || decorationInfo.isBottomRound()) ?
131                 mCornerGroupRadius : mCornerResultRadius;
132         float radiusTop =
133                 (decorationInfo == null || decorationInfo.isTopRound()) ?
134                         mCornerGroupRadius : mCornerResultRadius;
135         int color = isHighlighted ? mFocusColor : mFillColor;
136 
137         GradientDrawable shape = new GradientDrawable();
138         shape.setShape(GradientDrawable.RECTANGLE);
139         shape.setCornerRadii(new float[] {
140                 radiusTop, radiusTop, // top-left
141                 radiusTop, radiusTop, // top-right
142                 radiusBottom, radiusBottom, // bottom-right
143                 radiusBottom, radiusBottom // bottom-left
144         });
145         shape.setColor(color);
146 
147         // Setting the background resets the padding, so we cache it and reset it afterwards.
148         int paddingLeft = view.getPaddingLeft();
149         int paddingTop = view.getPaddingTop();
150         int paddingRight = view.getPaddingRight();
151         int paddingBottom = view.getPaddingBottom();
152 
153         view.setBackground(new InsetDrawable(shape, inset));
154 
155         view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
156     }
157 
158     /**
159      * Section decorator that combines views and draws a single block decoration
160      */
161     public static class UnionDecorationHandler extends SectionDecorationHandler {
162 
163         private final int mPaddingLeft;
164         private final int mPaddingRight;
165 
UnionDecorationHandler( SectionDecorationHandler decorationHandler, int paddingLeft, int paddingRight)166         public UnionDecorationHandler(
167                 SectionDecorationHandler decorationHandler,
168                 int paddingLeft, int paddingRight) {
169             super(decorationHandler.mContext, decorationHandler.mFillAlpha,
170                     decorationHandler.mIsTopLeftRound, decorationHandler.mIsTopRightRound,
171                     decorationHandler.mIsBottomLeftRound, decorationHandler.mIsBottomRightRound);
172             mPaddingLeft = paddingLeft;
173             mPaddingRight = paddingRight;
174         }
175 
176         /**
177          * Expands decoration bounds to include child {@link PrivateAppsSectionDecorator}
178          */
addChild(SectionDecorationHandler child, View view)179         public void addChild(SectionDecorationHandler child, View view) {
180             int scaledHeight = (int) (view.getHeight() * view.getScaleY());
181             mBounds.union(view.getLeft(), view.getY(),
182                     view.getRight(), view.getY() + scaledHeight);
183             mIsBottomRound |= child.mIsBottomRound;
184             mIsBottomLeftRound |= child.mIsBottomLeftRound;
185             mIsBottomRightRound |= child.mIsBottomRightRound;
186             mIsTopRound |= child.mIsTopRound;
187             mIsTopLeftRound |= child.mIsTopLeftRound;
188             mIsTopRightRound |= child.mIsTopRightRound;
189         }
190 
191         /**
192          * Draws group decoration to canvas
193          */
onGroupDecorate(Canvas canvas)194         public void onGroupDecorate(Canvas canvas) {
195             initCorners();
196             mBounds.left = mPaddingLeft;
197             mBounds.right = canvas.getWidth() - mPaddingRight;
198             mPaint.setColor(mFillColor);
199             mPaint.setAlpha(mFillAlpha);
200             onDraw(canvas);
201         }
202     }
203 }
204