1 /* 2 * Copyright (C) 2022 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.taskbar 18 19 import android.content.res.Resources 20 import android.graphics.Canvas 21 import android.graphics.Color 22 import android.graphics.Paint 23 import android.graphics.Path 24 import android.graphics.RectF 25 import com.android.app.animation.Interpolators 26 import com.android.launcher3.DeviceProfile 27 import com.android.launcher3.R 28 import com.android.launcher3.Utilities 29 import com.android.launcher3.Utilities.mapRange 30 import com.android.launcher3.Utilities.mapToRange 31 import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound 32 import com.android.launcher3.util.DisplayController 33 34 /** Helps draw the taskbar background, made up of a rectangle plus two inverted rounded corners. */ 35 class TaskbarBackgroundRenderer(context: TaskbarActivityContext) { 36 37 private val isInSetup: Boolean = !context.isUserSetupComplete 38 private val DARK_THEME_SHADOW_ALPHA = 51f 39 private val LIGHT_THEME_SHADOW_ALPHA = 25f 40 41 val paint = Paint() 42 val lastDrawnTransientRect = RectF() 43 var backgroundHeight = context.deviceProfile.taskbarHeight.toFloat() 44 var translationYForSwipe = 0f 45 var translationYForStash = 0f 46 47 private var maxBackgroundHeight = context.deviceProfile.taskbarHeight.toFloat() 48 private val transientBackgroundBounds = context.transientTaskbarBounds 49 50 private val isTransientTaskbar = DisplayController.isTransientTaskbar(context) 51 52 private val shadowAlpha: Float 53 private var shadowBlur = 0f 54 private var keyShadowDistance = 0f 55 private var bottomMargin = 0 56 57 private val fullLeftCornerRadius = context.leftCornerRadius.toFloat() 58 private val fullRightCornerRadius = context.rightCornerRadius.toFloat() 59 private var leftCornerRadius = fullLeftCornerRadius 60 private var rightCornerRadius = fullRightCornerRadius 61 private var widthInsetPercentage = 0f 62 private val square: Path = Path() 63 private val circle: Path = Path() 64 private val invertedLeftCornerPath: Path = Path() 65 private val invertedRightCornerPath: Path = Path() 66 67 private var stashedHandleWidth = 68 context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width) 69 70 private val stashedHandleHeight = 71 context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height) 72 73 init { 74 paint.color = context.getColor(R.color.taskbar_background) 75 paint.flags = Paint.ANTI_ALIAS_FLAG 76 paint.style = Paint.Style.FILL 77 78 if (isTransientTaskbar) { 79 val res = context.resources 80 bottomMargin = res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin) 81 shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur) 82 keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance) 83 } 84 85 shadowAlpha = 86 if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA 87 else LIGHT_THEME_SHADOW_ALPHA 88 89 setCornerRoundness(DEFAULT_ROUNDNESS) 90 } 91 updateStashedHandleWidthnull92 fun updateStashedHandleWidth(dp: DeviceProfile, res: Resources) { 93 stashedHandleWidth = res.getDimensionPixelSize( 94 if (TaskbarManager.isPhoneMode(dp)) R.dimen.taskbar_stashed_small_screen 95 else R.dimen.taskbar_stashed_handle_width 96 ) 97 } 98 99 /** 100 * Sets the roundness of the round corner above Taskbar. No effect on transient Taskkbar. 101 * 102 * @param cornerRoundness 0 has no round corner, 1 has complete round corner. 103 */ setCornerRoundnessnull104 fun setCornerRoundness(cornerRoundness: Float) { 105 if (isTransientTaskbar && !transientBackgroundBounds.isEmpty) { 106 return 107 } 108 109 leftCornerRadius = fullLeftCornerRadius * cornerRoundness 110 rightCornerRadius = fullRightCornerRadius * cornerRoundness 111 112 // Create the paths for the inverted rounded corners above the taskbar. Start with a filled 113 // square, and then subtract out a circle from the appropriate corner. 114 square.reset() 115 square.addRect(0f, 0f, leftCornerRadius, leftCornerRadius, Path.Direction.CW) 116 circle.reset() 117 circle.addCircle(leftCornerRadius, 0f, leftCornerRadius, Path.Direction.CW) 118 invertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE) 119 120 square.reset() 121 square.addRect(0f, 0f, rightCornerRadius, rightCornerRadius, Path.Direction.CW) 122 circle.reset() 123 circle.addCircle(0f, 0f, rightCornerRadius, Path.Direction.CW) 124 invertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE) 125 } 126 127 /** Draws the background with the given paint and height, on the provided canvas. */ drawnull128 fun draw(canvas: Canvas) { 129 canvas.save() 130 if (!isTransientTaskbar || transientBackgroundBounds.isEmpty) { 131 canvas.translate(0f, canvas.height - backgroundHeight - bottomMargin) 132 // Draw the background behind taskbar content. 133 canvas.drawRect(0f, 0f, canvas.width.toFloat(), backgroundHeight, paint) 134 135 // Draw the inverted rounded corners above the taskbar. 136 canvas.translate(0f, -leftCornerRadius) 137 canvas.drawPath(invertedLeftCornerPath, paint) 138 canvas.translate(0f, leftCornerRadius) 139 canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius) 140 canvas.drawPath(invertedRightCornerPath, paint) 141 } else if (!isInSetup) { 142 // backgroundHeight is a value from [0...maxBackgroundHeight], so we can use it as a 143 // proxy to figure out the animation progress of the stash/unstash animation. 144 val progress = backgroundHeight / maxBackgroundHeight 145 146 // At progress 0, we draw the background as the stashed handle. 147 // At progress 1, we draw the background as the full taskbar. 148 val newBackgroundHeight = 149 mapRange(progress, stashedHandleHeight.toFloat(), maxBackgroundHeight) 150 val fullWidth = transientBackgroundBounds.width() 151 val newWidth = mapRange(progress, stashedHandleWidth.toFloat(), fullWidth.toFloat()) 152 val halfWidthDelta = (fullWidth - newWidth) / 2f 153 val radius = newBackgroundHeight / 2f 154 val bottomMarginProgress = bottomMargin * ((1f - progress) / 2f) 155 156 // Aligns the bottom with the bottom of the stashed handle. 157 val bottom = 158 canvas.height - bottomMargin + 159 bottomMarginProgress + 160 translationYForSwipe + 161 translationYForStash + 162 -mapRange(1f - progress, 0f, stashedHandleHeight / 2f) 163 164 // Draw shadow. 165 val newShadowAlpha = 166 mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR) 167 paint.setShadowLayer( 168 shadowBlur, 169 0f, 170 keyShadowDistance, 171 setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha)) 172 ) 173 174 lastDrawnTransientRect.set( 175 transientBackgroundBounds.left + halfWidthDelta, 176 bottom - newBackgroundHeight, 177 transientBackgroundBounds.right - halfWidthDelta, 178 bottom 179 ) 180 val horizontalInset = fullWidth * widthInsetPercentage 181 lastDrawnTransientRect.inset(horizontalInset, 0f) 182 183 canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint) 184 } 185 canvas.restore() 186 } 187 188 /** 189 * Sets the width percentage to inset the transient taskbar's background from the left and from 190 * the right. 191 */ setBackgroundHorizontalInsetsnull192 fun setBackgroundHorizontalInsets(insetPercentage: Float) { 193 widthInsetPercentage = insetPercentage 194 } 195 196 companion object { 197 const val DEFAULT_ROUNDNESS = 1f 198 } 199 } 200