1 /*
<lambda>null2 * Copyright 2024 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 androidx.compose.material3
18
19 import androidx.compose.foundation.shape.CornerBasedShape
20 import androidx.compose.ui.Modifier
21 import androidx.compose.ui.geometry.Size
22 import androidx.compose.ui.graphics.Shape
23 import androidx.compose.ui.layout.layout
24 import androidx.compose.ui.unit.Dp
25 import androidx.compose.ui.unit.dp
26 import kotlin.math.roundToInt
27
28 /**
29 * [Modifier] that centers the content horizontally depending on the [CornerBasedShape] provided. It
30 * will increase or decrease the start padding to better center the content depending on the corner
31 * radii of the provided shape. This is meant to be used with asymmetric shapes, the modifier will
32 * not do anything to the content if the shape provided is symmetric.
33 *
34 * @param shape the [CornerBasedShape] that the content should be adjusted to so that the content is
35 * more centered within the shape.
36 * @param maxStartOffset the maximum start offset that the content can be adjusted before it starts
37 * clipping the content
38 * @param maxEndOffset the maximum end offset that the content can be adjusted before it starts
39 * clipping the content
40 */
41 @ExperimentalMaterial3ExpressiveApi
42 internal fun Modifier.horizontalCenterOptically(
43 shape: CornerBasedShape,
44 maxStartOffset: Dp = 0.dp,
45 maxEndOffset: Dp = 0.dp
46 ) =
47 this.layout { measureable, constraints ->
48 val placeable = measureable.measure(constraints)
49 val width = placeable.width
50 val height = placeable.height
51 val size = Size(width = width.toFloat(), height = height.toFloat())
52 val density = this@layout
53 val maxStartOffsetPx = -maxStartOffset.toPx()
54 val maxEndOffsetPx = maxEndOffset.toPx()
55
56 val topStart = shape.topStart.toPx(shapeSize = size, density = density)
57 val topEnd = shape.topEnd.toPx(shapeSize = size, density = density)
58 val bottomStart = shape.bottomStart.toPx(shapeSize = size, density = density)
59 val bottomEnd = shape.bottomEnd.toPx(shapeSize = size, density = density)
60 val avgStart = (topStart + bottomStart) / 2
61 val avgEnd = (topEnd + bottomEnd) / 2
62 val paddingCorrection = CenterOpticallyCoefficient * (avgStart - avgEnd)
63 layout(width, height) {
64 val coercedCorrection = paddingCorrection.coerceIn(maxStartOffsetPx, maxEndOffsetPx)
65 placeable.place(coercedCorrection.roundToInt(), 0)
66 }
67 }
68
69 @ExperimentalMaterial3ExpressiveApi
horizontalCenterOpticallynull70 internal fun Modifier.horizontalCenterOptically(
71 shape: ShapeWithHorizontalCenterOptically,
72 maxStartOffset: Dp = 0.dp,
73 maxEndOffset: Dp = 0.dp
74 ) =
75 this.layout { measurable, constraints ->
76 val placeable = measurable.measure(constraints)
77 val width = placeable.width
78 val height = placeable.height
79 val maxStartOffsetPx = -maxStartOffset.toPx()
80 val maxEndOffsetPx = maxEndOffset.toPx()
81 layout(width, height) {
82 val coercedOffset = shape.offset().coerceIn(maxStartOffsetPx, maxEndOffsetPx)
83 placeable.place(coercedOffset.roundToInt(), 0)
84 }
85 }
86
87 internal interface ShapeWithHorizontalCenterOptically : Shape {
offsetnull88 fun offset(): Float
89 }
90
91 internal const val CenterOpticallyCoefficient = 0.11f
92