1 /*
2  * Copyright (C) 2021 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.wm.shell.common.split;
18 
19 import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
20 import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
21 import static android.view.RoundedCorner.POSITION_TOP_LEFT;
22 import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
23 
24 import android.content.Context;
25 import android.graphics.Canvas;
26 import android.graphics.Paint;
27 import android.graphics.Path;
28 import android.graphics.Point;
29 import android.util.AttributeSet;
30 import android.view.RoundedCorner;
31 import android.view.View;
32 
33 import androidx.annotation.Nullable;
34 
35 import com.android.wm.shell.R;
36 
37 /**
38  * Draws inverted rounded corners beside divider bar to keep splitting tasks cropped with proper
39  * rounded corners.
40  */
41 public class DividerRoundedCorner extends View {
42     private final int mDividerWidth;
43     private final Paint mDividerBarBackground;
44     private final Point mStartPos = new Point();
45     private InvertedRoundedCornerDrawInfo mTopLeftCorner;
46     private InvertedRoundedCornerDrawInfo mTopRightCorner;
47     private InvertedRoundedCornerDrawInfo mBottomLeftCorner;
48     private InvertedRoundedCornerDrawInfo mBottomRightCorner;
49     private boolean mIsLeftRightSplit;
50 
DividerRoundedCorner(Context context, @Nullable AttributeSet attrs)51     public DividerRoundedCorner(Context context, @Nullable AttributeSet attrs) {
52         super(context, attrs);
53         mDividerWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_bar_width);
54         mDividerBarBackground = new Paint();
55         mDividerBarBackground.setColor(
56                 getResources().getColor(R.color.split_divider_background, null));
57         mDividerBarBackground.setFlags(Paint.ANTI_ALIAS_FLAG);
58         mDividerBarBackground.setStyle(Paint.Style.FILL);
59     }
60 
61     @Override
onAttachedToWindow()62     protected void onAttachedToWindow() {
63         super.onAttachedToWindow();
64         mTopLeftCorner = new InvertedRoundedCornerDrawInfo(POSITION_TOP_LEFT);
65         mTopRightCorner = new InvertedRoundedCornerDrawInfo(POSITION_TOP_RIGHT);
66         mBottomLeftCorner = new InvertedRoundedCornerDrawInfo(POSITION_BOTTOM_LEFT);
67         mBottomRightCorner = new InvertedRoundedCornerDrawInfo(POSITION_BOTTOM_RIGHT);
68     }
69 
70     @Override
onDraw(Canvas canvas)71     protected void onDraw(Canvas canvas) {
72         canvas.save();
73 
74         mTopLeftCorner.calculateStartPos(mStartPos);
75         canvas.translate(mStartPos.x, mStartPos.y);
76         canvas.drawPath(mTopLeftCorner.mPath, mDividerBarBackground);
77 
78         canvas.translate(-mStartPos.x, -mStartPos.y);
79         mTopRightCorner.calculateStartPos(mStartPos);
80         canvas.translate(mStartPos.x, mStartPos.y);
81         canvas.drawPath(mTopRightCorner.mPath, mDividerBarBackground);
82 
83         canvas.translate(-mStartPos.x, -mStartPos.y);
84         mBottomLeftCorner.calculateStartPos(mStartPos);
85         canvas.translate(mStartPos.x, mStartPos.y);
86         canvas.drawPath(mBottomLeftCorner.mPath, mDividerBarBackground);
87 
88         canvas.translate(-mStartPos.x, -mStartPos.y);
89         mBottomRightCorner.calculateStartPos(mStartPos);
90         canvas.translate(mStartPos.x, mStartPos.y);
91         canvas.drawPath(mBottomRightCorner.mPath, mDividerBarBackground);
92 
93         canvas.restore();
94     }
95 
96     @Override
hasOverlappingRendering()97     public boolean hasOverlappingRendering() {
98         return false;
99     }
100 
setIsLeftRightSplit(boolean isLeftRightSplit)101     void setIsLeftRightSplit(boolean isLeftRightSplit) {
102         mIsLeftRightSplit = isLeftRightSplit;
103     }
104 
105     /**
106      * Holds draw information of the inverted rounded corner at a specific position.
107      *
108      * @see {@link com.android.launcher3.taskbar.TaskbarDragLayer}
109      */
110     private class InvertedRoundedCornerDrawInfo {
111         @RoundedCorner.Position
112         private final int mCornerPosition;
113 
114         private final int mRadius;
115 
116         private final Path mPath = new Path();
117 
InvertedRoundedCornerDrawInfo(@oundedCorner.Position int cornerPosition)118         InvertedRoundedCornerDrawInfo(@RoundedCorner.Position int cornerPosition) {
119             mCornerPosition = cornerPosition;
120 
121             final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(cornerPosition);
122             mRadius = roundedCorner == null ? 0 : roundedCorner.getRadius();
123 
124             // Starts with a filled square, and then subtracting out a circle from the appropriate
125             // corner.
126             final Path square = new Path();
127             square.addRect(0, 0, mRadius, mRadius, Path.Direction.CW);
128             final Path circle = new Path();
129             circle.addCircle(
130                     isLeftCorner() ? mRadius : 0 /* x */,
131                     isTopCorner() ? mRadius : 0 /* y */,
132                     mRadius, Path.Direction.CW);
133             mPath.op(square, circle, Path.Op.DIFFERENCE);
134         }
135 
calculateStartPos(Point outPos)136         private void calculateStartPos(Point outPos) {
137             if (mIsLeftRightSplit) {
138                 // Place left corner at the right side of the divider bar.
139                 outPos.x = isLeftCorner()
140                         ? getWidth() / 2 + mDividerWidth / 2
141                         : getWidth() / 2 - mDividerWidth / 2 - mRadius;
142                 outPos.y = isTopCorner() ? 0 : getHeight() - mRadius;
143             } else {
144                 outPos.x = isLeftCorner() ? 0 : getWidth() - mRadius;
145                 // Place top corner at the bottom of the divider bar.
146                 outPos.y = isTopCorner()
147                         ? getHeight() / 2 + mDividerWidth / 2
148                         : getHeight() / 2 - mDividerWidth / 2 - mRadius;
149             }
150         }
151 
isLeftCorner()152         private boolean isLeftCorner() {
153             return mCornerPosition == POSITION_TOP_LEFT || mCornerPosition == POSITION_BOTTOM_LEFT;
154         }
155 
isTopCorner()156         private boolean isTopCorner() {
157             return mCornerPosition == POSITION_TOP_LEFT || mCornerPosition == POSITION_TOP_RIGHT;
158         }
159     }
160 }
161