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.vector
18 
19 import androidx.compose.runtime.AbstractApplier
20 import androidx.compose.runtime.Composable
21 import androidx.compose.runtime.ComposeNode
22 import androidx.compose.ui.graphics.Brush
23 import androidx.compose.ui.graphics.PathFillType
24 import androidx.compose.ui.graphics.PathMeasure
25 import androidx.compose.ui.graphics.StrokeCap
26 import androidx.compose.ui.graphics.StrokeJoin
27 import androidx.compose.ui.graphics.drawscope.Stroke
28 
29 /**
30  * Defines a group of [Path]s and other [Group]s inside a [VectorPainter]. This is not a regular UI
31  * composable, it can only be called inside composables called from the content parameter to
32  * [rememberVectorPainter].
33  *
34  * @param name Optional name of the group used when describing the vector as a string.
35  * @param rotation The rotation of the group around the Z axis, in degrees.
36  * @param pivotX The horizontal pivot point used for rotation, in pixels.
37  * @param pivotY The vertical pivot point used for rotation, in pixels.
38  * @param scaleX Factor to scale the group by horizontally.
39  * @param scaleY Factor to scale the group by vertically.
40  * @param translationX Horizontal offset of the group, in pixels.
41  * @param translationY Vertical offset of the group, in pixels.
42  * @param clipPathData A list of [PathNode]s that define how to clip the group. Empty by default.
43  * @param content A composable that defines the contents of the group.
44  */
45 @Composable
46 @VectorComposable
Groupnull47 fun Group(
48     name: String = DefaultGroupName,
49     rotation: Float = DefaultRotation,
50     pivotX: Float = DefaultPivotX,
51     pivotY: Float = DefaultPivotY,
52     scaleX: Float = DefaultScaleX,
53     scaleY: Float = DefaultScaleY,
54     translationX: Float = DefaultTranslationX,
55     translationY: Float = DefaultTranslationY,
56     clipPathData: List<PathNode> = EmptyPath,
57     content: @Composable @VectorComposable () -> Unit
58 ) {
59     ComposeNode<GroupComponent, VectorApplier>(
60         factory = { GroupComponent() },
61         update = {
62             set(name) { this.name = it }
63             set(rotation) { this.rotation = it }
64             set(pivotX) { this.pivotX = it }
65             set(pivotY) { this.pivotY = it }
66             set(scaleX) { this.scaleX = it }
67             set(scaleY) { this.scaleY = it }
68             set(translationX) { this.translationX = it }
69             set(translationY) { this.translationY = it }
70             set(clipPathData) { this.clipPathData = it }
71         }
72     ) {
73         content()
74     }
75 }
76 
77 /**
78  * Defines a path inside a [VectorPainter]. This is not a regular UI composable, it can only be
79  * called inside composables called from the content parameter to [rememberVectorPainter].
80  *
81  * @param pathData List of [PathNode]s that define the path.
82  * @param pathFillType The [PathFillType] that specifies how to fill the path.
83  * @param name Optional name of the path used when describing the vector as a string.
84  * @param fill The [Brush] used to fill the path.
85  * @param fillAlpha The alpha value to use for [fill].
86  * @param stroke The [Brush] used to stroke the path.
87  * @param strokeAlpha The alpha value to use for [stroke].
88  * @param strokeLineWidth The width of the [stroke]. See [Stroke.width] for details.
89  * @param strokeLineCap The [StrokeCap] of [stroke]. See [Stroke.cap] for details.
90  * @param strokeLineJoin The [StrokeJoin] of [stroke]. See [Stroke.join] for details.
91  * @param strokeLineMiter The stroke miter value. See [Stroke.miter] for details.
92  * @param trimPathStart The fraction of the path that specifies the start of the clipped region of
93  *   the path. See [PathMeasure.getSegment].
94  * @param trimPathEnd The fraction of the path that specifies the end of the clipped region of the
95  *   path. See [PathMeasure.getSegment].
96  * @param trimPathOffset The amount to offset both [trimPathStart] and [trimPathEnd].
97  */
98 @Composable
99 @VectorComposable
Pathnull100 fun Path(
101     pathData: List<PathNode>,
102     pathFillType: PathFillType = DefaultFillType,
103     name: String = DefaultPathName,
104     fill: Brush? = null,
105     fillAlpha: Float = 1.0f,
106     stroke: Brush? = null,
107     strokeAlpha: Float = 1.0f,
108     strokeLineWidth: Float = DefaultStrokeLineWidth,
109     strokeLineCap: StrokeCap = DefaultStrokeLineCap,
110     strokeLineJoin: StrokeJoin = DefaultStrokeLineJoin,
111     strokeLineMiter: Float = DefaultStrokeLineMiter,
112     trimPathStart: Float = DefaultTrimPathStart,
113     trimPathEnd: Float = DefaultTrimPathEnd,
114     trimPathOffset: Float = DefaultTrimPathOffset
115 ) {
116     ComposeNode<PathComponent, VectorApplier>(
117         factory = { PathComponent() },
118         update = {
119             set(name) { this.name = it }
120             set(pathData) { this.pathData = it }
121             set(pathFillType) { this.pathFillType = it }
122             set(fill) { this.fill = it }
123             set(fillAlpha) { this.fillAlpha = it }
124             set(stroke) { this.stroke = it }
125             set(strokeAlpha) { this.strokeAlpha = it }
126             set(strokeLineWidth) { this.strokeLineWidth = it }
127             set(strokeLineJoin) { this.strokeLineJoin = it }
128             set(strokeLineCap) { this.strokeLineCap = it }
129             set(strokeLineMiter) { this.strokeLineMiter = it }
130             set(trimPathStart) { this.trimPathStart = it }
131             set(trimPathEnd) { this.trimPathEnd = it }
132             set(trimPathOffset) { this.trimPathOffset = it }
133         }
134     )
135 }
136 
137 class VectorApplier(root: VNode) : AbstractApplier<VNode>(root) {
insertTopDownnull138     override fun insertTopDown(index: Int, instance: VNode) {
139         // Ignored as the tree is built bottom-up.
140     }
141 
insertBottomUpnull142     override fun insertBottomUp(index: Int, instance: VNode) {
143         current.asGroup().insertAt(index, instance)
144     }
145 
removenull146     override fun remove(index: Int, count: Int) {
147         current.asGroup().remove(index, count)
148     }
149 
onClearnull150     override fun onClear() {
151         root.asGroup().let { it.remove(0, it.numChildren) }
152     }
153 
movenull154     override fun move(from: Int, to: Int, count: Int) {
155         current.asGroup().move(from, to, count)
156     }
157 
VNodenull158     private fun VNode.asGroup(): GroupComponent {
159         return when (this) {
160             is GroupComponent -> this
161             else -> error("Cannot only insert VNode into Group")
162         }
163     }
164 }
165