1 /*
2  * Copyright 2020 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.graphics
18 
19 import androidx.compose.runtime.Immutable
20 
21 /**
22  * Effect applied to the geometry of a drawing primitive. For example, this can be used to draw
23  * shapes as a dashed or shaped pattern, or apply a treatment around line segment intersections.
24  */
25 interface PathEffect {
26     companion object {
27 
28         /**
29          * Replaces sharp angles between line segments into rounded angles of the specified radius
30          *
31          * @param radius Rounded corner radius to apply for each angle of the drawn shape
32          */
cornerPathEffectnull33         fun cornerPathEffect(radius: Float): PathEffect = actualCornerPathEffect(radius)
34 
35         /**
36          * Draws a shape as a series of dashes with the given intervals and offset into the
37          * specified interval array. The intervals must contain an even number of entries (>=2). The
38          * even indices specify "on" intervals and the odd indices represent "off" intervals. The
39          * phase parameter is the pixel offset into the intervals array (mod the sum of all of the
40          * intervals).
41          *
42          * For example: if `intervals[] = {10, 20}`, and phase = 25, this will set up a dashed path
43          * like so: 5 pixels off 10 pixels on 20 pixels off 10 pixels on 20 pixels off
44          *
45          * The phase parameter is an offset into the intervals array. The intervals array controls
46          * the length of the dashes. This is only applied for stroked shapes (ex.
47          * [PaintingStyle.Stroke] and is ignored for filled in shapes (ex. [PaintingStyle.Fill]
48          *
49          * @param intervals Array of "on" and "off" distances for the dashed line segments
50          * @param phase Pixel offset into the intervals array
51          */
52         fun dashPathEffect(intervals: FloatArray, phase: Float = 0f): PathEffect =
53             actualDashPathEffect(intervals, phase)
54 
55         /**
56          * Create a PathEffect that applies the inner effect to the path, and then applies the outer
57          * effect to the result of the inner effect. (e.g. outer(inner(path)).
58          */
59         fun chainPathEffect(outer: PathEffect, inner: PathEffect): PathEffect =
60             actualChainPathEffect(outer, inner)
61 
62         /**
63          * Dash the drawn path by stamping it with the specified shape represented as a [Path]. This
64          * is only applied to stroke shapes and will be ignored with filled shapes. The stroke width
65          * used with this [PathEffect] is ignored as well.
66          *
67          * @param shape Path to stamp along
68          * @param advance Spacing between each stamped shape
69          * @param phase Amount to offset before the first shape is stamped
70          * @param style How to transform the shape at each position as it is stamped
71          */
72         fun stampedPathEffect(
73             shape: Path,
74             advance: Float,
75             phase: Float,
76             style: StampedPathEffectStyle
77         ): PathEffect = actualStampedPathEffect(shape, advance, phase, style)
78     }
79 }
80 
81 internal expect fun actualCornerPathEffect(radius: Float): PathEffect
82 
83 internal expect fun actualDashPathEffect(intervals: FloatArray, phase: Float): PathEffect
84 
85 internal expect fun actualChainPathEffect(outer: PathEffect, inner: PathEffect): PathEffect
86 
87 internal expect fun actualStampedPathEffect(
88     shape: Path,
89     advance: Float,
90     phase: Float,
91     style: StampedPathEffectStyle
92 ): PathEffect
93 
94 /**
95  * Strategy for transforming each point of the shape along the drawn path
96  *
97  * @sample androidx.compose.ui.graphics.samples.StampedPathEffectSample
98  */
99 @Immutable
100 @kotlin.jvm.JvmInline
101 value class StampedPathEffectStyle
102 internal constructor(@Suppress("unused") private val value: Int) {
103 
104     companion object {
105         /**
106          * Translate the path shape into the specified location aligning the top left of the path
107          * with the drawn geometry. This does not modify the path itself.
108          *
109          * For example, a circle drawn with a square path and [Translate] will draw the square path
110          * repeatedly with the top left corner of each stamped square along the curvature of the
111          * circle.
112          */
113         val Translate = StampedPathEffectStyle(0)
114 
115         /**
116          * Rotates the path shape its center along the curvature of the drawn geometry. This does
117          * not modify the path itself.
118          *
119          * For example, a circle drawn with a square path and [Rotate] will draw the square path
120          * repeatedly with the center of each stamped square along the curvature of the circle as
121          * well as each square being rotated along the circumference.
122          */
123         val Rotate = StampedPathEffectStyle(1)
124 
125         /**
126          * Modifies the points within the path such that they fit within the drawn geometry. This
127          * will turn straight lines into curves.
128          *
129          * For example, a circle drawn with a square path and [Morph] will modify the straight lines
130          * of the square paths to be curves such that each stamped square is rendered as an arc
131          * around the curvature of the circle.
132          */
133         val Morph = StampedPathEffectStyle(2)
134     }
135 
136     override fun toString() =
137         when (this) {
138             Translate -> "Translate"
139             Rotate -> "Rotate"
140             Morph -> "Morph"
141             else -> "Unknown"
142         }
143 }
144