1 /*
2  * 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.ui.text
18 
19 import androidx.compose.ui.geometry.CornerRadius
20 import androidx.compose.ui.geometry.RoundRect
21 import androidx.compose.ui.geometry.Size
22 import androidx.compose.ui.geometry.toRect
23 import androidx.compose.ui.graphics.Brush
24 import androidx.compose.ui.graphics.Outline
25 import androidx.compose.ui.graphics.Shape
26 import androidx.compose.ui.graphics.drawscope.DrawStyle
27 import androidx.compose.ui.graphics.drawscope.Fill
28 import androidx.compose.ui.unit.Density
29 import androidx.compose.ui.unit.LayoutDirection
30 import androidx.compose.ui.unit.TextUnit
31 import androidx.compose.ui.unit.em
32 
33 /**
34  * A bullet annotation applied to the AnnotatedString that draws a bullet.
35  *
36  * Note that it's up to the caller of this API to make sure there's enough space between the edge of
37  * the text composable and the start of the paragraph inside that composable to fit the bullet with
38  * its padding.
39  *
40  * @param shape a shape of the bullet to draw
41  * @param size a size of the bullet
42  * @param padding a padding between the end of the bullet and the start of the paragraph
43  * @param brush a brush to draw a bullet with
44  * @param alpha an alpha to apply when drawing a bullet
45  * @param drawStyle defines the draw style of the bullet, e.g. a fill or an outline
46  */
47 internal class Bullet(
48     val shape: Shape,
49     val size: TextUnit, // Make TextUnitSize or something similar when making public
50     val padding: TextUnit,
51     val brush: Brush?,
52     val alpha: Float,
53     val drawStyle: DrawStyle
54 ) : AnnotatedString.Annotation {
55     /** Copies the existing [Bullet] replacing some of the fields as desired. */
copynull56     fun copy(
57         shape: Shape = this.shape,
58         size: TextUnit = this.size,
59         padding: TextUnit = this.padding,
60         brush: Brush? = this.brush,
61         alpha: Float = this.alpha,
62         drawStyle: DrawStyle = this.drawStyle,
63     ) = Bullet(shape, size, padding, brush, alpha, drawStyle)
64 
65     override fun equals(other: Any?): Boolean {
66         if (this === other) return true
67         if (other == null || other !is Bullet) return false
68 
69         if (shape != other.shape) return false
70         if (size != other.size) return false
71         if (padding != other.padding) return false
72         if (brush != other.brush) return false
73         if (alpha != other.alpha) return false
74         if (drawStyle != other.drawStyle) return false
75 
76         return true
77     }
78 
hashCodenull79     override fun hashCode(): Int {
80         var result = shape.hashCode()
81         result = 31 * result + size.hashCode()
82         result = 31 * result + padding.hashCode()
83         result = 31 * result + brush.hashCode()
84         result = 31 * result + alpha.hashCode()
85         result = 31 * result + drawStyle.hashCode()
86         return result
87     }
88 
toStringnull89     override fun toString(): String {
90         return "Bullet(shape=$shape, size=$size, padding=$padding, brush=$brush, alpha=$alpha, drawStyle=$drawStyle)"
91     }
92 }
93 
94 /** Default indentation between the start edge of the Text composable and start of paragraph */
95 internal val DefaultBulletIndentation = 1.em
96 /** Default size of the bullet */
97 private val DefaultBulletSize = 0.25.em
98 /** Default padding between a bullet and start of a paragraph */
99 private val DefaultBulletPadding = 0.25.em
100 /** Default bullet used in AnnotatedString's bullet list */
101 internal val DefaultBullet =
102     Bullet(CircleShape, DefaultBulletSize, DefaultBulletPadding, null, 1f, Fill)
103 
104 private object CircleShape : Shape {
createOutlinenull105     override fun createOutline(
106         size: Size,
107         layoutDirection: LayoutDirection,
108         density: Density
109     ): Outline {
110         val cornerRadius = CornerRadius(size.minDimension / 2f)
111         return Outline.Rounded(
112             RoundRect(
113                 rect = size.toRect(),
114                 topLeft = cornerRadius,
115                 topRight = cornerRadius,
116                 bottomRight = cornerRadius,
117                 bottomLeft = cornerRadius
118             )
119         )
120     }
121 }
122