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