• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 package com.android.quickstep.views
17 
18 import android.content.Context
19 import android.graphics.Canvas
20 import android.graphics.Rect
21 import android.util.AttributeSet
22 import android.util.FloatProperty
23 import android.widget.Button
24 import com.android.launcher3.Flags.enableFocusOutline
25 import com.android.launcher3.R
26 import com.android.launcher3.util.KFloatProperty
27 import com.android.launcher3.util.MultiPropertyDelegate
28 import com.android.launcher3.util.MultiValueAlpha
29 import com.android.quickstep.util.BorderAnimator
30 import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
31 import kotlin.math.abs
32 import kotlin.math.min
33 
34 class ClearAllButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
35     Button(context, attrs) {
36 
37     private val clearAllButtonAlpha =
38         object : MultiValueAlpha(this, Alpha.entries.size) {
applynull39             override fun apply(value: Float) {
40                 super.apply(value)
41                 isClickable = value >= 1f
42             }
43         }
44     var scrollAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.SCROLL)
45     var contentAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.CONTENT)
46     var visibilityAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.VISIBILITY)
47     var dismissAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.DISMISS)
48 
49     var fullscreenProgress = 1f
50         set(value) {
51             if (field == value) {
52                 return
53             }
54             field = value
55             applyPrimaryTranslation()
56         }
57 
58     /**
59      * Moves ClearAllButton between carousel and 2 row grid.
60      *
61      * 0 = carousel; 1 = 2 row grid.
62      */
63     var gridProgress = 1f
64         set(value) {
65             if (field == value) {
66                 return
67             }
68             field = value
69             applyPrimaryTranslation()
70         }
71 
72     private var normalTranslationPrimary = 0f
73     var fullscreenTranslationPrimary = 0f
74         set(value) {
75             if (field == value) {
76                 return
77             }
78             field = value
79             applyPrimaryTranslation()
80         }
81 
82     var gridTranslationPrimary = 0f
83         set(value) {
84             if (field == value) {
85                 return
86             }
87             field = value
88             applyPrimaryTranslation()
89         }
90 
91     /** Used to put the button at the middle in the secondary coordinate. */
92     var taskAlignmentTranslationY = 0f
93         set(value) {
94             if (field == value) {
95                 return
96             }
97             field = value
98             applySecondaryTranslation()
99         }
100 
101     var gridScrollOffset = 0f
102     var scrollOffsetPrimary = 0f
103 
104     private var sidePadding = 0
105     var borderEnabled = false
106         set(value) {
107             if (field == value) {
108                 return
109             }
110             field = value
111             focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
112         }
113 
114     private val focusBorderAnimator: BorderAnimator? =
115         if (enableFocusOutline())
116             createSimpleBorderAnimator(
117                 context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_radius),
118                 context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width),
119                 this::getBorderBounds,
120                 this,
121                 context
122                     .obtainStyledAttributes(attrs, R.styleable.ClearAllButton)
123                     .getColor(
124                         R.styleable.ClearAllButton_focusBorderColor,
125                         BorderAnimator.DEFAULT_BORDER_COLOR,
126                     ),
127             )
128         else null
129 
getBorderBoundsnull130     private fun getBorderBounds(bounds: Rect) {
131         bounds.set(0, 0, width, height)
132         val outlinePadding =
133             context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_padding)
134         // Make the value negative to form a padding between button and outline
135         bounds.inset(-outlinePadding, -outlinePadding)
136     }
137 
onFocusChangednull138     public override fun onFocusChanged(
139         gainFocus: Boolean,
140         direction: Int,
141         previouslyFocusedRect: Rect?,
142     ) {
143         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
144         if (borderEnabled) {
145             focusBorderAnimator?.setBorderVisibility(gainFocus, /* animated= */ true)
146         }
147     }
148 
drawnull149     override fun draw(canvas: Canvas) {
150         focusBorderAnimator?.drawBorder(canvas)
151         super.draw(canvas)
152     }
153 
onLayoutnull154     override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
155         super.onLayout(changed, left, top, right, bottom)
156         sidePadding =
157             recentsView?.let { it.pagedOrientationHandler?.getClearAllSidePadding(it, isLayoutRtl) }
158                 ?: 0
159     }
160 
161     private val recentsView: RecentsView<*, *>?
162         get() = parent as? RecentsView<*, *>?
163 
hasOverlappingRenderingnull164     override fun hasOverlappingRendering() = false
165 
166     fun onRecentsViewScroll(scroll: Int, gridEnabled: Boolean) {
167         val recentsView = recentsView ?: return
168 
169         val orientationSize =
170             recentsView.pagedOrientationHandler.getPrimaryValue(width, height).toFloat()
171         if (orientationSize == 0f) {
172             return
173         }
174 
175         val clearAllScroll = recentsView.clearAllScroll
176         val adjustedScrollFromEdge = abs((scroll - clearAllScroll)).toFloat()
177         val shift = min(adjustedScrollFromEdge, orientationSize)
178         normalTranslationPrimary = if (isLayoutRtl) -shift else shift
179         if (!gridEnabled) {
180             normalTranslationPrimary += sidePadding.toFloat()
181         }
182         applyPrimaryTranslation()
183         applySecondaryTranslation()
184         var clearAllSpacing = recentsView.pageSpacing + recentsView.clearAllExtraPageSpacing
185         clearAllSpacing = if (isLayoutRtl) -clearAllSpacing else clearAllSpacing
186         scrollAlpha =
187             ((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing.toFloat()).coerceAtLeast(
188                 0f
189             )
190     }
191 
getScrollAdjustmentnull192     fun getScrollAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean): Float {
193         var scrollAdjustment = 0f
194         if (fullscreenEnabled) {
195             scrollAdjustment += fullscreenTranslationPrimary
196         }
197         if (gridEnabled) {
198             scrollAdjustment += gridTranslationPrimary + gridScrollOffset
199         }
200         scrollAdjustment += scrollOffsetPrimary
201         return scrollAdjustment
202     }
203 
getOffsetAdjustmentnull204     fun getOffsetAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean) =
205         getScrollAdjustment(fullscreenEnabled, gridEnabled)
206 
207     private fun applyPrimaryTranslation() {
208         val recentsView = recentsView ?: return
209         val orientationHandler = recentsView.pagedOrientationHandler
210         orientationHandler.primaryViewTranslate.set(
211             this,
212             (orientationHandler.getPrimaryValue(0f, taskAlignmentTranslationY) +
213                 normalTranslationPrimary +
214                 getFullscreenTrans(fullscreenTranslationPrimary) +
215                 getGridTrans(gridTranslationPrimary)),
216         )
217     }
218 
applySecondaryTranslationnull219     private fun applySecondaryTranslation() {
220         val recentsView = recentsView ?: return
221         val orientationHandler = recentsView.pagedOrientationHandler
222         orientationHandler.secondaryViewTranslate.set(
223             this,
224             orientationHandler.getSecondaryValue(0f, taskAlignmentTranslationY),
225         )
226     }
227 
getFullscreenTransnull228     private fun getFullscreenTrans(endTranslation: Float) =
229         if (fullscreenProgress > 0) endTranslation else 0f
230 
231     private fun getGridTrans(endTranslation: Float) = if (gridProgress > 0) endTranslation else 0f
232 
233     companion object {
234         private enum class Alpha {
235             SCROLL,
236             CONTENT,
237             VISIBILITY,
238             DISMISS,
239         }
240 
241         @JvmField
242         val VISIBILITY_ALPHA: FloatProperty<ClearAllButton> =
243             KFloatProperty(ClearAllButton::visibilityAlpha)
244 
245         @JvmField
246         val DISMISS_ALPHA: FloatProperty<ClearAllButton> =
247             KFloatProperty(ClearAllButton::dismissAlpha)
248     }
249 }
250