1 /*
2  * Copyright 2025 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 package androidx.wear.protolayout.renderer.inflater
17 
18 import androidx.annotation.ColorInt
19 import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicColor
20 import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicFloat
21 import androidx.wear.protolayout.expression.proto.DynamicProto.StateColorSource
22 import androidx.wear.protolayout.expression.proto.DynamicProto.StateFloatSource
23 import androidx.wear.protolayout.expression.proto.FixedProto.FixedColor
24 import androidx.wear.protolayout.expression.proto.FixedProto.FixedFloat
25 import androidx.wear.protolayout.proto.ColorProto.Brush
26 import androidx.wear.protolayout.proto.ColorProto.ColorProp
27 import androidx.wear.protolayout.proto.ColorProto.ColorStop
28 import androidx.wear.protolayout.proto.ColorProto.LinearGradient
29 import androidx.wear.protolayout.proto.ColorProto.SweepGradient
30 import androidx.wear.protolayout.proto.DimensionProto.BoundingBoxRatio
31 import androidx.wear.protolayout.proto.DimensionProto.ContainerDimension
32 import androidx.wear.protolayout.proto.DimensionProto.DegreesProp
33 import androidx.wear.protolayout.proto.DimensionProto.DpProp
34 import androidx.wear.protolayout.proto.DimensionProto.ExpandedDimensionProp
35 import androidx.wear.protolayout.proto.DimensionProto.OffsetDimension
36 import androidx.wear.protolayout.proto.LayoutElementProto.Box
37 import androidx.wear.protolayout.proto.LayoutElementProto.LayoutElement
38 import androidx.wear.protolayout.proto.ModifiersProto.Background
39 import androidx.wear.protolayout.proto.ModifiersProto.Modifiers
40 import androidx.wear.protolayout.proto.TypesProto.FloatProp
41 import androidx.wear.protolayout.renderer.helper.TestFingerprinter
42 
43 /**
44  * Kotlin helpers for creating ProtoLayout elements in proto format. To be used to simplify kt
45  * tests.
46  */
47 object ProtoLayoutHelpers {
fingerprintedLayoutnull48     fun fingerprintedLayout(root: LayoutElement) =
49         TestFingerprinter.getDefault().buildLayoutWithFingerprints(root)
50 
51     fun expandedBox(block: Box.Builder.() -> Unit) =
52         LayoutElement.newBuilder()
53             .setBox(Box.newBuilder().setWidth(expand()).setHeight(expand()).apply(block))
54             .build()
55 
56     fun expand() =
57         ContainerDimension.newBuilder()
58             .setExpandedDimension(ExpandedDimensionProp.getDefaultInstance())
59 
60     fun modifiers(block: Modifiers.Builder.() -> Unit) = Modifiers.newBuilder().apply(block).build()
61 
62     fun background(block: Background.Builder.() -> Unit) =
63         Background.newBuilder().apply(block).build()
64 
65     fun linearGradient(
66         vararg colorStops: ColorStop,
67         block: LinearGradient.Builder.() -> Unit = {}
68     ) = LinearGradient.newBuilder().addAllColorStops(colorStops.toList()).apply(block).build()
69 
<lambda>null70     fun sweepGradient(vararg colorStops: ColorStop, block: SweepGradient.Builder.() -> Unit = {}) =
71         SweepGradient.newBuilder().addAllColorStops(colorStops.toList()).apply(block).build()
72 
73     val LinearGradient.toBrush: Brush
74         get() = Brush.newBuilder().setLinearGradient(this).build()
75 
boundingBoxRationull76     fun boundingBoxRatio(ratio: Float) = BoundingBoxRatio.newBuilder().setRatio(ratio.prop).build()
77 
78     val BoundingBoxRatio.toOffset: OffsetDimension
79         get() = OffsetDimension.newBuilder().setLocationRatio(this).build()
80 
81     val DpProp.toOffset: OffsetDimension
82         get() = OffsetDimension.newBuilder().setOffsetDp(this).build()
83 
84     val Float.dpProp: DpProp
85         get() = DpProp.newBuilder().setValue(this).build()
86 
87     val Float.prop: FloatProp
88         get() = floatProp(this)
89 
90     val Float.degreesProp: DegreesProp
91         get() = DegreesProp.newBuilder().setValue(this).build()
92 
93     /** Builds a [FloatProp]. */
94     fun floatProp(v: Float, dynamicValue: DynamicFloat? = null): FloatProp =
95         FloatProp.newBuilder()
96             .setValue(v)
97             .apply { dynamicValue?.let { setDynamicValue(it) } }
98             .build()
99 
100     /** Builds a [DegreesProp]. */
degreesPropnull101     fun degreesProp(v: Float, dynamicValue: DynamicFloat? = null): DegreesProp =
102         DegreesProp.newBuilder()
103             .setValue(v)
104             .apply { dynamicValue?.let { setDynamicValue(it) } }
105             .build()
106 
107     /** Builds a [ColorStop]. */
colorStopnull108     fun colorStop(@ColorInt color: Int, offset: Float? = null): ColorStop =
109         colorStop(colorProp(color), offset?.prop)
110 
111     /** Builds a [ColorProp]. */
112     fun colorProp(@ColorInt color: Int, dynamicColor: DynamicColor? = null): ColorProp =
113         ColorProp.newBuilder()
114             .setArgb(color)
115             .apply { dynamicColor?.let { setDynamicValue(it) } }
116             .build()
117 
118     /** Builds a [ColorStop]. */
colorStopnull119     fun colorStop(colorProp: ColorProp, offset: FloatProp? = null): ColorStop =
120         ColorStop.newBuilder().setColor(colorProp).apply { offset?.let { setOffset(it) } }.build()
121 
122     /** Builds a [DynamicColor] with a fixed value. */
fixedDynamicColornull123     fun fixedDynamicColor(@ColorInt v: Int): DynamicColor =
124         DynamicColor.newBuilder().setFixed(FixedColor.newBuilder().setArgb(v)).build()
125 
126     /** Builds a [DynamicColor] with a state source. */
127     fun stateDynamicColor(key: String): DynamicColor =
128         DynamicColor.newBuilder()
129             .setStateSource(StateColorSource.newBuilder().setSourceKey(key))
130             .build()
131 
132     /** Builds a [DynamicFloat]. */
133     fun fixedDynamicFloat(v: Float): DynamicFloat =
134         DynamicFloat.newBuilder().setFixed(FixedFloat.newBuilder().setValue(v)).build()
135 
136     /** Builds a [DynamicFloat] with a state source. */
137     fun stateDynamicFloat(key: String): DynamicFloat =
138         DynamicFloat.newBuilder()
139             .setStateSource(StateFloatSource.newBuilder().setSourceKey(key))
140             .build()
141 }
142