• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     private boolean mIsSplitScreen;
51 
DividerRoundedCorner(Context context, @Nullable AttributeSet attrs)52     public DividerRoundedCorner(Context context, @Nullable AttributeSet attrs) {
53         super(context, attrs);
54         mDividerWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_bar_width);
55         mDividerBarBackground = new Paint();
56         mDividerBarBackground.setColor(
57                 getResources().getColor(R.color.split_divider_background, null /* theme */));
58         mDividerBarBackground.setFlags(Paint.ANTI_ALIAS_FLAG);
59         mDividerBarBackground.setStyle(Paint.Style.FILL);
60         mIsSplitScreen = false;
61     }
62 
63     @Override
onAttachedToWindow()64     protected void onAttachedToWindow() {
65         super.onAttachedToWindow();
66         mTopLeftCorner = new InvertedRoundedCornerDrawInfo(POSITION_TOP_LEFT);
67         mTopRightCorner = new InvertedRoundedCornerDrawInfo(POSITION_TOP_RIGHT);
68         mBottomLeftCorner = new InvertedRoundedCornerDrawInfo(POSITION_BOTTOM_LEFT);
69         mBottomRightCorner = new InvertedRoundedCornerDrawInfo(POSITION_BOTTOM_RIGHT);
70     }
71 
72     @Override
onDraw(Canvas canvas)73     protected void onDraw(Canvas canvas) {
74         canvas.save();
75 
76         mTopLeftCorner.calculateStartPos(mStartPos);
77         canvas.translate(mStartPos.x, mStartPos.y);
78         canvas.drawPath(mTopLeftCorner.mPath, mDividerBarBackground);
79 
80         canvas.translate(-mStartPos.x, -mStartPos.y);
81         mTopRightCorner.calculateStartPos(mStartPos);
82         canvas.translate(mStartPos.x, mStartPos.y);
83         canvas.drawPath(mTopRightCorner.mPath, mDividerBarBackground);
84 
85         canvas.translate(-mStartPos.x, -mStartPos.y);
86         mBottomLeftCorner.calculateStartPos(mStartPos);
87         canvas.translate(mStartPos.x, mStartPos.y);
88         canvas.drawPath(mBottomLeftCorner.mPath, mDividerBarBackground);
89 
90         canvas.translate(-mStartPos.x, -mStartPos.y);
91         mBottomRightCorner.calculateStartPos(mStartPos);
92         canvas.translate(mStartPos.x, mStartPos.y);
93         canvas.drawPath(mBottomRightCorner.mPath, mDividerBarBackground);
94 
95         canvas.restore();
96     }
97 
98     @Override
hasOverlappingRendering()99     public boolean hasOverlappingRendering() {
100         return false;
101     }
102 
103     /**
104      * Used by tiling infrastructure to specify display light/dark mode and
105      * whether handle colors should be overridden on display mode change in case
106      * of non split screen.
107      *
108      * @param isSplitScreen Whether the divider is used by split screen or tiling.
109      * @param color         Rounded corner color.
110      */
setup(boolean isSplitScreen, int color)111     public void setup(boolean isSplitScreen, int color) {
112         mIsSplitScreen = isSplitScreen;
113         if (!isSplitScreen) {
114             mDividerBarBackground.setColor(color);
115         }
116     }
117 
118     /**
119      * Notifies the divider of ui mode change and provides a new color.
120      *
121      * @param color The new divider rounded corner color.
122      */
onUiModeChange(int color)123     public void onUiModeChange(int color) {
124         if (!mIsSplitScreen) {
125             mDividerBarBackground.setColor(color);
126             invalidate();
127         }
128     }
129 
130     /**
131      * Notifies rounded corner view of color change.
132      *
133      * @param color The new divider rounded corner color.
134      */
onCornerColorChange(int color)135     public void onCornerColorChange(int color) {
136         if (!mIsSplitScreen) {
137             mDividerBarBackground.setColor(color);
138             invalidate();
139         }
140     }
141 
142     /**
143      * Set whether the rounded corner is for a left/right split.
144      *
145      * @param isLeftRightSplit whether it's a left/right split or top/bottom split.
146      */
setIsLeftRightSplit(boolean isLeftRightSplit)147     public void setIsLeftRightSplit(boolean isLeftRightSplit) {
148         mIsLeftRightSplit = isLeftRightSplit;
149     }
150 
151     /**
152      * Holds draw information of the inverted rounded corner at a specific position.
153      *
154      * @see {@link com.android.launcher3.taskbar.TaskbarDragLayer}
155      */
156     private class InvertedRoundedCornerDrawInfo {
157         @RoundedCorner.Position
158         private final int mCornerPosition;
159 
160         private final int mRadius;
161 
162         private final Path mPath = new Path();
163 
InvertedRoundedCornerDrawInfo(@oundedCorner.Position int cornerPosition)164         InvertedRoundedCornerDrawInfo(@RoundedCorner.Position int cornerPosition) {
165             mCornerPosition = cornerPosition;
166 
167             final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(cornerPosition);
168             if (mIsSplitScreen) {
169                 mRadius = roundedCorner == null ? 0 : roundedCorner.getRadius();
170             } else {
171                 mRadius = mContext
172                         .getResources()
173                         .getDimensionPixelSize(
174                                 com.android.wm.shell.shared.R.dimen
175                                         .desktop_windowing_freeform_rounded_corner_radius);
176             }
177 
178 
179             // Starts with a filled square, and then subtracting out a circle from the appropriate
180             // corner.
181             final Path square = new Path();
182             square.addRect(0, 0, mRadius, mRadius, Path.Direction.CW);
183             final Path circle = new Path();
184             circle.addCircle(
185                     isLeftCorner() ? mRadius : 0 /* x */,
186                     isTopCorner() ? mRadius : 0 /* y */,
187                     mRadius, Path.Direction.CW);
188             mPath.op(square, circle, Path.Op.DIFFERENCE);
189         }
190 
calculateStartPos(Point outPos)191         private void calculateStartPos(Point outPos) {
192             if (mIsLeftRightSplit) {
193                 // Place left corner at the right side of the divider bar.
194                 outPos.x = isLeftCorner()
195                         ? getWidth() / 2 + mDividerWidth / 2
196                         : getWidth() / 2 - mDividerWidth / 2 - mRadius;
197                 outPos.y = isTopCorner() ? 0 : getHeight() - mRadius;
198             } else {
199                 outPos.x = isLeftCorner() ? 0 : getWidth() - mRadius;
200                 // Place top corner at the bottom of the divider bar.
201                 outPos.y = isTopCorner()
202                         ? getHeight() / 2 + mDividerWidth / 2
203                         : getHeight() / 2 - mDividerWidth / 2 - mRadius;
204             }
205         }
206 
isLeftCorner()207         private boolean isLeftCorner() {
208             return mCornerPosition == POSITION_TOP_LEFT || mCornerPosition == POSITION_BOTTOM_LEFT;
209         }
210 
isTopCorner()211         private boolean isTopCorner() {
212             return mCornerPosition == POSITION_TOP_LEFT || mCornerPosition == POSITION_TOP_RIGHT;
213         }
214     }
215 }
216