• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.launcher3.apppairs;
18 
19 import android.graphics.Canvas;
20 import android.graphics.ColorFilter;
21 import android.graphics.Paint;
22 import android.graphics.PixelFormat;
23 import android.graphics.RectF;
24 import android.graphics.drawable.Drawable;
25 import android.os.Build;
26 
27 import androidx.annotation.NonNull;
28 
29 import com.android.launcher3.Utilities;
30 import com.android.launcher3.icons.FastBitmapDrawable;
31 
32 /**
33  * A composed Drawable consisting of the two app pair icons and the background behind them (looks
34  * like two rectangles).
35  */
36 public class AppPairIconDrawable extends Drawable {
37     private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
38     private final AppPairIconDrawingParams mP;
39     private final FastBitmapDrawable mIcon1;
40     private final FastBitmapDrawable mIcon2;
41 
42     /**
43      * Null values to use with
44      * {@link Canvas#drawDoubleRoundRect(RectF, float[], RectF, float[], Paint)}, since there
45      * doesn't seem to be any other API for drawing rectangles with 4 different corner radii.
46      */
47     private static final RectF EMPTY_RECT = new RectF();
48     private static final float[] ARRAY_OF_ZEROES = new float[8];
49 
AppPairIconDrawable( AppPairIconDrawingParams p, FastBitmapDrawable icon1, FastBitmapDrawable icon2)50     AppPairIconDrawable(
51             AppPairIconDrawingParams p, FastBitmapDrawable icon1, FastBitmapDrawable icon2) {
52         mP = p;
53         mBackgroundPaint.setStyle(Paint.Style.FILL);
54         mBackgroundPaint.setColor(p.getBgColor());
55         mIcon1 = icon1;
56         mIcon2 = icon2;
57     }
58 
59     @Override
draw(@onNull Canvas canvas)60     public void draw(@NonNull Canvas canvas) {
61         if (mP.isLeftRightSplit()) {
62             drawLeftRightSplit(canvas);
63         } else {
64             drawTopBottomSplit(canvas);
65         }
66 
67         canvas.translate(
68                 mP.getStandardIconPadding() + mP.getOuterPadding(),
69                 mP.getStandardIconPadding() + mP.getOuterPadding()
70         );
71 
72         // Draw first icon.
73         canvas.save();
74         // The app icons are placed differently depending on device orientation.
75         if (mP.isLeftRightSplit()) {
76             canvas.translate(
77                     mP.getInnerPadding(),
78                     mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f
79             );
80         } else {
81             canvas.translate(
82                     mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f,
83                     mP.getInnerPadding()
84             );
85         }
86 
87         mIcon1.draw(canvas);
88         canvas.restore();
89 
90         // Draw second icon.
91         canvas.save();
92         // The app icons are placed differently depending on device orientation.
93         if (mP.isLeftRightSplit()) {
94             canvas.translate(
95                     mP.getBackgroundSize() - (mP.getInnerPadding() + mP.getMemberIconSize()),
96                     mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f
97             );
98         } else {
99             canvas.translate(
100                     mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f,
101                     mP.getBackgroundSize() - (mP.getInnerPadding() + mP.getMemberIconSize())
102             );
103         }
104 
105         mIcon2.draw(canvas);
106         canvas.restore();
107     }
108 
109     /**
110      * When device is in landscape, we draw the rectangles with a left-right split.
111      */
drawLeftRightSplit(Canvas canvas)112     private void drawLeftRightSplit(Canvas canvas) {
113         // Get the bounds where we will draw the background image
114         int width = mP.getIconSize();
115         int height = mP.getIconSize();
116 
117         // The left half of the background image, excluding center channel
118         RectF leftSide = new RectF(
119                 mP.getStandardIconPadding() + mP.getOuterPadding(),
120                 mP.getStandardIconPadding() + mP.getOuterPadding(),
121                 (width / 2f) - (mP.getCenterChannelSize() / 2f),
122                 height - (mP.getStandardIconPadding() + mP.getOuterPadding())
123         );
124         // The right half of the background image, excluding center channel
125         RectF rightSide = new RectF(
126                 (width / 2f) + (mP.getCenterChannelSize() / 2f),
127                 (mP.getStandardIconPadding() + mP.getOuterPadding()),
128                 width - (mP.getStandardIconPadding() + mP.getOuterPadding()),
129                 height - (mP.getStandardIconPadding() + mP.getOuterPadding())
130         );
131 
132         // Scale each background from its center edge closest to the center channel.
133         Utilities.scaleRectFAboutPivot(
134                 leftSide,
135                 leftSide.left + leftSide.width(),
136                 leftSide.top + leftSide.centerY(),
137                 mP.getHoverScale());
138         Utilities.scaleRectFAboutPivot(
139                 rightSide,
140                 rightSide.left,
141                 rightSide.top + rightSide.centerY(),
142                 mP.getHoverScale());
143 
144         drawCustomRoundedRect(canvas, leftSide, new float[]{
145                 mP.getBigRadius(), mP.getBigRadius(),
146                 mP.getSmallRadius(), mP.getSmallRadius(),
147                 mP.getSmallRadius(), mP.getSmallRadius(),
148                 mP.getBigRadius(), mP.getBigRadius()});
149         drawCustomRoundedRect(canvas, rightSide, new float[]{
150                 mP.getSmallRadius(), mP.getSmallRadius(),
151                 mP.getBigRadius(), mP.getBigRadius(),
152                 mP.getBigRadius(), mP.getBigRadius(),
153                 mP.getSmallRadius(), mP.getSmallRadius()});
154     }
155 
156     /**
157      * When device is in portrait, we draw the rectangles with a top-bottom split.
158      */
drawTopBottomSplit(Canvas canvas)159     private void drawTopBottomSplit(Canvas canvas) {
160         // Get the bounds where we will draw the background image
161         int width = mP.getIconSize();
162         int height = mP.getIconSize();
163 
164         // The top half of the background image, excluding center channel
165         RectF topSide = new RectF(
166                 (mP.getStandardIconPadding() + mP.getOuterPadding()),
167                 (mP.getStandardIconPadding() + mP.getOuterPadding()),
168                 width - (mP.getStandardIconPadding() + mP.getOuterPadding()),
169                 (height / 2f) - (mP.getCenterChannelSize() / 2f)
170         );
171         // The bottom half of the background image, excluding center channel
172         RectF bottomSide = new RectF(
173                 (mP.getStandardIconPadding() + mP.getOuterPadding()),
174                 (height / 2f) + (mP.getCenterChannelSize() / 2f),
175                 width - (mP.getStandardIconPadding() + mP.getOuterPadding()),
176                 height - (mP.getStandardIconPadding() + mP.getOuterPadding())
177         );
178 
179         // Scale each background from its center edge closest to the center channel.
180         Utilities.scaleRectFAboutPivot(
181                 topSide,
182                 topSide.left + topSide.centerX(),
183                 topSide.top + topSide.height(),
184                 mP.getHoverScale());
185         Utilities.scaleRectFAboutPivot(
186                 bottomSide,
187                 bottomSide.left + bottomSide.centerX(),
188                 bottomSide.top,
189                 mP.getHoverScale());
190 
191         drawCustomRoundedRect(canvas, topSide, new float[]{
192                 mP.getBigRadius(), mP.getBigRadius(),
193                 mP.getBigRadius(), mP.getBigRadius(),
194                 mP.getSmallRadius(), mP.getSmallRadius(),
195                 mP.getSmallRadius(), mP.getSmallRadius()});
196         drawCustomRoundedRect(canvas, bottomSide, new float[]{
197                 mP.getSmallRadius(), mP.getSmallRadius(),
198                 mP.getSmallRadius(), mP.getSmallRadius(),
199                 mP.getBigRadius(), mP.getBigRadius(),
200                 mP.getBigRadius(), mP.getBigRadius()});
201     }
202 
203     /**
204      * Draws a rectangle with custom rounded corners.
205      * @param c The Canvas to draw on.
206      * @param rect The bounds of the rectangle.
207      * @param radii An array of 8 radii for the corners: top left x, top left y, top right x, top
208      *              right y, bottom right x, and so on.
209      */
drawCustomRoundedRect(Canvas c, RectF rect, float[] radii)210     private void drawCustomRoundedRect(Canvas c, RectF rect, float[] radii) {
211         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
212             // Canvas.drawDoubleRoundRect is supported from Q onward
213             c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, mBackgroundPaint);
214         } else {
215             // Fallback rectangle with uniform rounded corners
216             c.drawRoundRect(rect, mP.getBigRadius(), mP.getBigRadius(), mBackgroundPaint);
217         }
218     }
219 
220     @Override
getOpacity()221     public int getOpacity() {
222         return PixelFormat.OPAQUE;
223     }
224 
225     @Override
setAlpha(int i)226     public void setAlpha(int i) {
227         mBackgroundPaint.setAlpha(i);
228     }
229 
230     @Override
setColorFilter(ColorFilter colorFilter)231     public void setColorFilter(ColorFilter colorFilter) {
232         mBackgroundPaint.setColorFilter(colorFilter);
233     }
234 
235     @Override
getIntrinsicWidth()236     public int getIntrinsicWidth() {
237         return mP.getIconSize();
238     }
239 
240     @Override
getIntrinsicHeight()241     public int getIntrinsicHeight() {
242         return mP.getIconSize();
243     }
244 }
245