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