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