• 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.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