• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2023 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.systemui.ribbon.ui.composable
18 
19 import androidx.compose.foundation.background
20 import androidx.compose.foundation.layout.Box
21 import androidx.compose.runtime.Composable
22 import androidx.compose.ui.Modifier
23 import androidx.compose.ui.draw.drawWithCache
24 import androidx.compose.ui.graphics.Color
25 import androidx.compose.ui.graphics.ColorFilter
26 import androidx.compose.ui.graphics.ColorMatrix
27 import androidx.compose.ui.graphics.graphicsLayer
28 import androidx.compose.ui.graphics.layer.drawLayer
29 import androidx.compose.ui.layout.layout
30 import com.android.compose.modifiers.thenIf
31 import kotlin.math.PI
32 import kotlin.math.cos
33 import kotlin.math.roundToInt
34 import kotlin.math.sin
35 import kotlin.math.tan
36 
37 /**
38  * Renders a "ribbon" at the bottom right corner of its container.
39  *
40  * The [content] is rendered leaning at an angle of [degrees] degrees (between `1` and `89`,
41  * inclusive), with an alpha of [alpha] (between `0f` and `1f`, inclusive).
42  *
43  * The background color of the strip can be modified by passing a value to the [backgroundColor] or
44  * `null` to remove the strip background.
45  *
46  * The [colorSaturation] is a function that returns that amount of color saturation to apply to the
47  * entire ribbon. If it's `1`, the full color will be used, if it's `0` it will be greyscale.
48  *
49  * Note: this function assumes that it's been placed at the bottom right of its parent by its
50  * caller. It's the caller's responsibility to meet that assumption by actually placing this
51  * composable element at the bottom right.
52  */
53 @Composable
54 fun BottomRightCornerRibbon(
55     content: @Composable () -> Unit,
56     modifier: Modifier = Modifier,
57     degrees: Int = 45,
58     alpha: Float = 0.6f,
59     colorSaturation: () -> Float = { 1f },
60     backgroundColor: Color? = Color.Red,
61 ) {
62     check(degrees in 1..89)
63     check(alpha in 0f..1f)
64 
65     val radians = degrees * (PI / 180)
66 
67     Box(
<lambda>null68         content = { content() },
69         modifier =
70             modifier
<lambda>null71                 .graphicsLayer {
72                     this.alpha = alpha
73 
74                     val w = size.width
75                     val h = size.height
76 
77                     val sine = sin(radians).toFloat()
78                     val cosine = cos(radians).toFloat()
79 
80                     translationX = (w - w * cosine + h * sine) / 2f
81                     translationY = (h - w * sine + h * cosine) / 2f
82                     rotationZ = 360f - degrees
83                 }
<lambda>null84                 .drawWithCache {
85                     val layer =
86                         obtainGraphicsLayer().apply {
87                             record {
88                                 colorFilter =
89                                     ColorFilter.colorMatrix(
90                                         colorMatrix =
91                                             ColorMatrix().apply {
92                                                 setToSaturation(colorSaturation())
93                                             }
94                                     )
95                                 drawContent()
96                             }
97                         }
98                     onDrawWithContent { drawLayer(layer) }
99                 }
<lambda>null100                 .thenIf(backgroundColor != null) { Modifier.background(backgroundColor!!) }
constraintsnull101                 .layout { measurable, constraints ->
102                     val placeable = measurable.measure(constraints)
103 
104                     val tangent = tan(radians)
105                     val leftPadding = (placeable.measuredHeight / tangent).roundToInt()
106                     val rightPadding = (placeable.measuredHeight * tangent).roundToInt()
107 
108                     layout(
109                         width = placeable.measuredWidth + leftPadding + rightPadding,
110                         height = placeable.measuredHeight,
111                     ) {
112                         placeable.place(leftPadding, 0)
113                     }
114                 },
115     )
116 }
117