• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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