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