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