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