blob: ddfea0325c06c9aac322e1fb4c6086ee46586c5f [file] [log] [blame]
/*
* Copyright 2021-2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.wear.protolayout;
import static androidx.wear.protolayout.expression.Preconditions.checkNotNull;
import android.annotation.SuppressLint;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.wear.protolayout.ColorBuilders.ColorProp;
import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
import androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint;
import androidx.wear.protolayout.DimensionBuilders.ContainerDimension;
import androidx.wear.protolayout.DimensionBuilders.DegreesProp;
import androidx.wear.protolayout.DimensionBuilders.DpProp;
import androidx.wear.protolayout.DimensionBuilders.EmProp;
import androidx.wear.protolayout.DimensionBuilders.HorizontalLayoutConstraint;
import androidx.wear.protolayout.DimensionBuilders.ImageDimension;
import androidx.wear.protolayout.DimensionBuilders.SpProp;
import androidx.wear.protolayout.DimensionBuilders.SpacerDimension;
import androidx.wear.protolayout.DimensionBuilders.VerticalLayoutConstraint;
import androidx.wear.protolayout.ModifiersBuilders.ArcModifiers;
import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
import androidx.wear.protolayout.ModifiersBuilders.SpanModifiers;
import androidx.wear.protolayout.TypeBuilders.BoolProp;
import androidx.wear.protolayout.TypeBuilders.Int32Prop;
import androidx.wear.protolayout.TypeBuilders.StringLayoutConstraint;
import androidx.wear.protolayout.TypeBuilders.StringProp;
import androidx.wear.protolayout.expression.Fingerprint;
import androidx.wear.protolayout.expression.ProtoLayoutExperimental;
import androidx.wear.protolayout.proto.AlignmentProto;
import androidx.wear.protolayout.proto.DimensionProto;
import androidx.wear.protolayout.proto.FingerprintProto;
import androidx.wear.protolayout.proto.FingerprintProto.TreeFingerprint;
import androidx.wear.protolayout.proto.LayoutElementProto;
import androidx.wear.protolayout.proto.TypesProto;
import androidx.wear.protolayout.protobuf.InvalidProtocolBufferException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** Builders for composable layout elements that can be combined together to create renderable UI */
public final class LayoutElementBuilders {
private LayoutElementBuilders() {}
/** The weight to be applied to the font. */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({FONT_WEIGHT_UNDEFINED, FONT_WEIGHT_NORMAL, FONT_WEIGHT_MEDIUM, FONT_WEIGHT_BOLD})
@Retention(RetentionPolicy.SOURCE)
public @interface FontWeight {}
/** Font weight is undefined. */
public static final int FONT_WEIGHT_UNDEFINED = 0;
/** Normal font weight. */
public static final int FONT_WEIGHT_NORMAL = 400;
/** Medium font weight. */
@ProtoLayoutExperimental public static final int FONT_WEIGHT_MEDIUM = 500;
/** Bold font weight. */
public static final int FONT_WEIGHT_BOLD = 700;
/**
* The variant of a font. Some renderers may use different fonts for title and body text, which
* can be selected using this field.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({FONT_VARIANT_UNDEFINED, FONT_VARIANT_TITLE, FONT_VARIANT_BODY})
@Retention(RetentionPolicy.SOURCE)
public @interface FontVariant {}
/** Font variant is undefined. */
public static final int FONT_VARIANT_UNDEFINED = 0;
/** Font variant suited for title text. */
public static final int FONT_VARIANT_TITLE = 1;
/** Font variant suited for body text. */
public static final int FONT_VARIANT_BODY = 2;
/**
* The alignment of a {@link SpanImage} within the line height of the surrounding {@link
* Spannable}.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({
SPAN_VERTICAL_ALIGN_UNDEFINED,
SPAN_VERTICAL_ALIGN_BOTTOM,
SPAN_VERTICAL_ALIGN_TEXT_BASELINE
})
@Retention(RetentionPolicy.SOURCE)
public @interface SpanVerticalAlignment {}
/** Alignment is undefined. */
public static final int SPAN_VERTICAL_ALIGN_UNDEFINED = 0;
/**
* Align to the bottom of the line (descent of the largest text in this line). If there is no
* text in the line containing this image, this will align to the bottom of the line, where the
* line height is defined as the height of the largest image in the line.
*/
public static final int SPAN_VERTICAL_ALIGN_BOTTOM = 1;
/**
* Align to the baseline of the text. Note that if the line in the {@link Spannable} which
* contains this image does not contain any text, the effects of using this alignment are
* undefined.
*/
public static final int SPAN_VERTICAL_ALIGN_TEXT_BASELINE = 2;
/**
* How text that will not fit inside the bounds of a {@link Text} element will be handled.
*
* @since 1.0
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({
TEXT_OVERFLOW_UNDEFINED,
TEXT_OVERFLOW_TRUNCATE,
TEXT_OVERFLOW_ELLIPSIZE_END,
TEXT_OVERFLOW_MARQUEE
})
@Retention(RetentionPolicy.SOURCE)
@OptIn(markerClass = ProtoLayoutExperimental.class)
public @interface TextOverflow {}
/**
* Overflow behavior is undefined.
*
* @since 1.0
*/
public static final int TEXT_OVERFLOW_UNDEFINED = 0;
/**
* Truncate the text to fit inside of the {@link Text} element's bounds. If text is truncated,
* it will be truncated on a word boundary.
*
* @since 1.0
*/
public static final int TEXT_OVERFLOW_TRUNCATE = 1;
/**
* Truncate the text to fit in the {@link Text} element's bounds, but add an ellipsis (i.e. ...)
* to the end of the text if it has been truncated.
*
* @since 1.0
*/
public static final int TEXT_OVERFLOW_ELLIPSIZE_END = 2;
/**
* Enable marquee animation for texts that don't fit inside the {@link Text} element. This is
* only applicable for single line texts; if the text has multiple lines, the behavior is
* equivalent to TEXT_OVERFLOW_TRUNCATE.
*
* @since 1.2
*/
@ProtoLayoutExperimental public static final int TEXT_OVERFLOW_MARQUEE = 3;
/**
* How content which does not match the dimensions of its bounds (e.g. an image resource being
* drawn inside an {@link Image}) will be resized to fit its bounds.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({
CONTENT_SCALE_MODE_UNDEFINED,
CONTENT_SCALE_MODE_FIT,
CONTENT_SCALE_MODE_CROP,
CONTENT_SCALE_MODE_FILL_BOUNDS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ContentScaleMode {}
/** Content scaling is undefined. */
public static final int CONTENT_SCALE_MODE_UNDEFINED = 0;
/**
* Content will be scaled to fit inside its bounds, proportionally. As an example, If a 10x5
* image was going to be drawn inside a 50x50 {@link Image} element, the actual image resource
* would be drawn as a 50x25 image, centered within the 50x50 bounds.
*/
public static final int CONTENT_SCALE_MODE_FIT = 1;
/**
* Content will be resized proportionally so it completely fills its bounds, and anything
* outside of the bounds will be cropped. As an example, if a 10x5 image was going to be drawn
* inside a 50x50 {@link Image} element, the image resource would be drawn as a 100x50 image,
* centered within its bounds (and with 25px cropped from both the left and right sides).
*/
public static final int CONTENT_SCALE_MODE_CROP = 2;
/**
* Content will be resized to fill its bounds, without taking into account the aspect ratio. If
* a 10x5 image was going to be drawn inside a 50x50 {@link Image} element, the image would be
* drawn as a 50x50 image, stretched vertically.
*/
public static final int CONTENT_SCALE_MODE_FILL_BOUNDS = 3;
/** An extensible {@code FontWeight} property. */
public static final class FontWeightProp {
private final LayoutElementProto.FontWeightProp mImpl;
@Nullable private final Fingerprint mFingerprint;
FontWeightProp(LayoutElementProto.FontWeightProp impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the value. Intended for testing purposes only. */
@FontWeight
public int getValue() {
return mImpl.getValue().getNumber();
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static FontWeightProp fromProto(@NonNull LayoutElementProto.FontWeightProp proto) {
return new FontWeightProp(proto, null);
}
@NonNull
LayoutElementProto.FontWeightProp toProto() {
return mImpl;
}
/** Builder for {@link FontWeightProp} */
public static final class Builder {
private final LayoutElementProto.FontWeightProp.Builder mImpl =
LayoutElementProto.FontWeightProp.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(1793388920);
public Builder() {}
/** Sets the value. */
@NonNull
public Builder setValue(@FontWeight int value) {
mImpl.setValue(LayoutElementProto.FontWeight.forNumber(value));
mFingerprint.recordPropertyUpdate(1, value);
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public FontWeightProp build() {
return new FontWeightProp(mImpl.build(), mFingerprint);
}
}
}
/** An extensible {@code FontVariant} property. */
@ProtoLayoutExperimental
public static final class FontVariantProp {
private final LayoutElementProto.FontVariantProp mImpl;
@Nullable private final Fingerprint mFingerprint;
FontVariantProp(
LayoutElementProto.FontVariantProp impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the value. Intended for testing purposes only. */
@FontVariant
public int getValue() {
return mImpl.getValue().getNumber();
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static FontVariantProp fromProto(@NonNull LayoutElementProto.FontVariantProp proto) {
return new FontVariantProp(proto, null);
}
@NonNull
LayoutElementProto.FontVariantProp toProto() {
return mImpl;
}
/** Builder for {@link FontVariantProp} */
public static final class Builder {
private final LayoutElementProto.FontVariantProp.Builder mImpl =
LayoutElementProto.FontVariantProp.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-293831500);
public Builder() {}
/** Sets the value. */
@NonNull
public Builder setValue(@FontVariant int value) {
mImpl.setValue(LayoutElementProto.FontVariant.forNumber(value));
mFingerprint.recordPropertyUpdate(1, value);
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public FontVariantProp build() {
return new FontVariantProp(mImpl.build(), mFingerprint);
}
}
}
/** An extensible {@code SpanVerticalAlignment} property. */
public static final class SpanVerticalAlignmentProp {
private final LayoutElementProto.SpanVerticalAlignmentProp mImpl;
@Nullable private final Fingerprint mFingerprint;
SpanVerticalAlignmentProp(
LayoutElementProto.SpanVerticalAlignmentProp impl,
@Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the value. Intended for testing purposes only. */
@SpanVerticalAlignment
public int getValue() {
return mImpl.getValue().getNumber();
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static SpanVerticalAlignmentProp fromProto(
@NonNull LayoutElementProto.SpanVerticalAlignmentProp proto) {
return new SpanVerticalAlignmentProp(proto, null);
}
@NonNull
LayoutElementProto.SpanVerticalAlignmentProp toProto() {
return mImpl;
}
/** Builder for {@link SpanVerticalAlignmentProp} */
public static final class Builder {
private final LayoutElementProto.SpanVerticalAlignmentProp.Builder mImpl =
LayoutElementProto.SpanVerticalAlignmentProp.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(1008812329);
public Builder() {}
/** Sets the value. */
@NonNull
public Builder setValue(@SpanVerticalAlignment int value) {
mImpl.setValue(LayoutElementProto.SpanVerticalAlignment.forNumber(value));
mFingerprint.recordPropertyUpdate(1, value);
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public SpanVerticalAlignmentProp build() {
return new SpanVerticalAlignmentProp(mImpl.build(), mFingerprint);
}
}
}
/** The styling of a font (e.g. font size, and metrics). */
public static final class FontStyle {
private final LayoutElementProto.FontStyle mImpl;
@Nullable private final Fingerprint mFingerprint;
FontStyle(LayoutElementProto.FontStyle impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets the size of the font, in scaled pixels (sp). If not specified, defaults to the size
* of the system's "body" font. Intended for testing purposes only.
*/
@Nullable
public SpProp getSize() {
if (mImpl.hasSize()) {
return SpProp.fromProto(mImpl.getSize());
} else {
return null;
}
}
/**
* Gets whether the text should be rendered in a italic typeface. If not specified, defaults
* to "false". Intended for testing purposes only.
*/
@Nullable
public BoolProp getItalic() {
if (mImpl.hasItalic()) {
return BoolProp.fromProto(mImpl.getItalic());
} else {
return null;
}
}
/**
* Gets whether the text should be rendered with an underline. If not specified, defaults to
* "false". Intended for testing purposes only.
*/
@Nullable
public BoolProp getUnderline() {
if (mImpl.hasUnderline()) {
return BoolProp.fromProto(mImpl.getUnderline());
} else {
return null;
}
}
/**
* Gets the text color. If not defined, defaults to white. Intended for testing purposes
* only.
*/
@Nullable
public ColorProp getColor() {
if (mImpl.hasColor()) {
return ColorProp.fromProto(mImpl.getColor());
} else {
return null;
}
}
/**
* Gets the weight of the font. If the provided value is not supported on a platform, the
* nearest supported value will be used. If not defined, or when set to an invalid value,
* defaults to "normal". Intended for testing purposes only.
*/
@Nullable
public FontWeightProp getWeight() {
if (mImpl.hasWeight()) {
return FontWeightProp.fromProto(mImpl.getWeight());
} else {
return null;
}
}
/**
* Gets the text letter-spacing. Positive numbers increase the space between letters while
* negative numbers tighten the space. If not specified, defaults to 0. Intended for testing
* purposes only.
*/
@Nullable
public EmProp getLetterSpacing() {
if (mImpl.hasLetterSpacing()) {
return EmProp.fromProto(mImpl.getLetterSpacing());
} else {
return null;
}
}
/**
* Gets the variant of a font. Some renderers may use different fonts for title and body
* text, which can be selected using this field. If not specified, defaults to "body".
* Intended for testing purposes only.
*/
@ProtoLayoutExperimental
@Nullable
public FontVariantProp getVariant() {
if (mImpl.hasVariant()) {
return FontVariantProp.fromProto(mImpl.getVariant());
} else {
return null;
}
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static FontStyle fromProto(@NonNull LayoutElementProto.FontStyle proto) {
return new FontStyle(proto, null);
}
/** Returns the internal proto instance. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.FontStyle toProto() {
return mImpl;
}
/** Builder for {@link FontStyle} */
public static final class Builder {
private final LayoutElementProto.FontStyle.Builder mImpl =
LayoutElementProto.FontStyle.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(181264306);
public Builder() {}
/**
* Sets the size of the font, in scaled pixels (sp). If not specified, defaults to the
* size of the system's "body" font.
*/
@NonNull
public Builder setSize(@NonNull SpProp size) {
mImpl.setSize(size.toProto());
mFingerprint.recordPropertyUpdate(
1, checkNotNull(size.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets whether the text should be rendered in a italic typeface. If not specified,
* defaults to "false".
*/
@NonNull
public Builder setItalic(@NonNull BoolProp italic) {
mImpl.setItalic(italic.toProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(italic.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets whether the text should be rendered in a italic typeface. If not specified,
* defaults to "false".
*/
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setItalic(boolean italic) {
mImpl.setItalic(TypesProto.BoolProp.newBuilder().setValue(italic));
return this;
}
/**
* Sets whether the text should be rendered with an underline. If not specified,
* defaults to "false".
*/
@NonNull
public Builder setUnderline(@NonNull BoolProp underline) {
mImpl.setUnderline(underline.toProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(underline.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets whether the text should be rendered with an underline. If not specified,
* defaults to "false".
*/
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setUnderline(boolean underline) {
mImpl.setUnderline(TypesProto.BoolProp.newBuilder().setValue(underline));
return this;
}
/**
* Sets the text color. If not defined, defaults to white.
*
* <p>This field is made bindable from version 1.2 and will use the dynamic value (if
* set). Older renderers will still consider this field as non-bindable and will use the
* static value.
*
* @since 1.0
*/
@NonNull
public Builder setColor(@NonNull ColorProp color) {
mImpl.setColor(color.toProto());
mFingerprint.recordPropertyUpdate(
4, checkNotNull(color.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the weight of the font. If the provided value is not supported on a platform,
* the nearest supported value will be used. If not defined, or when set to an invalid
* value, defaults to "normal".
*/
@NonNull
public Builder setWeight(@NonNull FontWeightProp weight) {
mImpl.setWeight(weight.toProto());
mFingerprint.recordPropertyUpdate(
5, checkNotNull(weight.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the weight of the font. If the provided value is not supported on a platform,
* the nearest supported value will be used. If not defined, or when set to an invalid
* value, defaults to "normal".
*/
@NonNull
public Builder setWeight(@FontWeight int weight) {
mImpl.setWeight(
LayoutElementProto.FontWeightProp.newBuilder()
.setValue(LayoutElementProto.FontWeight.forNumber(weight)));
return this;
}
/**
* Sets the text letter-spacing. Positive numbers increase the space between letters
* while negative numbers tighten the space. If not specified, defaults to 0.
*/
@NonNull
public Builder setLetterSpacing(@NonNull EmProp letterSpacing) {
mImpl.setLetterSpacing(letterSpacing.toProto());
mFingerprint.recordPropertyUpdate(
6, checkNotNull(letterSpacing.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the variant of a font. Some renderers may use different fonts for title and body
* text, which can be selected using this field. If not specified, defaults to "body".
*/
@ProtoLayoutExperimental
@NonNull
public Builder setVariant(@NonNull FontVariantProp variant) {
mImpl.setVariant(variant.toProto());
return this;
}
/**
* Sets the variant of a font. Some renderers may use different fonts for title and body
* text, which can be selected using this field. If not specified, defaults to "body".
*/
@ProtoLayoutExperimental
@NonNull
public Builder setVariant(@FontVariant int variant) {
mImpl.setVariant(
LayoutElementProto.FontVariantProp.newBuilder()
.setValue(LayoutElementProto.FontVariant.forNumber(variant)));
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public FontStyle build() {
return new FontStyle(mImpl.build(), mFingerprint);
}
}
}
/**
* An extensible {@code TextOverflow} property.
*
* @since 1.0
*/
public static final class TextOverflowProp {
private final LayoutElementProto.TextOverflowProp mImpl;
@Nullable private final Fingerprint mFingerprint;
TextOverflowProp(
LayoutElementProto.TextOverflowProp impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets the value.
*
* @since 1.0
*/
@TextOverflow
public int getValue() {
return mImpl.getValue().getNumber();
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
/** Creates a new wrapper instance from the proto. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public static TextOverflowProp fromProto(
@NonNull LayoutElementProto.TextOverflowProp proto,
@Nullable Fingerprint fingerprint) {
return new TextOverflowProp(proto, fingerprint);
}
@NonNull
static TextOverflowProp fromProto(@NonNull LayoutElementProto.TextOverflowProp proto) {
return fromProto(proto, null);
}
/** Returns the internal proto instance. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.TextOverflowProp toProto() {
return mImpl;
}
@Override
@NonNull
public String toString() {
return "TextOverflowProp{" + "value=" + getValue() + "}";
}
/** Builder for {@link TextOverflowProp} */
public static final class Builder {
private final LayoutElementProto.TextOverflowProp.Builder mImpl =
LayoutElementProto.TextOverflowProp.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-1542057565);
public Builder() {}
/**
* Sets the value.
*
* @since 1.0
*/
@NonNull
public Builder setValue(@TextOverflow int value) {
mImpl.setValue(LayoutElementProto.TextOverflow.forNumber(value));
mFingerprint.recordPropertyUpdate(1, value);
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public TextOverflowProp build() {
return new TextOverflowProp(mImpl.build(), mFingerprint);
}
}
}
/**
* Parameters for Marquee animation. Only applies for TEXT_OVERFLOW_MARQUEE.
*
* @since 1.2
*/
@ProtoLayoutExperimental
static final class MarqueeParameters {
private final LayoutElementProto.MarqueeParameters mImpl;
@Nullable private final Fingerprint mFingerprint;
MarqueeParameters(
LayoutElementProto.MarqueeParameters impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets the number of times to repeat the Marquee animation. Set to -1 to repeat
* indefinitely. Defaults to repeat indefinitely.
*
* @since 1.2
*/
@ProtoLayoutExperimental
public int getIterations() {
return mImpl.getIterations();
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
/** Creates a new wrapper instance from the proto. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public static MarqueeParameters fromProto(
@NonNull LayoutElementProto.MarqueeParameters proto,
@Nullable Fingerprint fingerprint) {
return new MarqueeParameters(proto, fingerprint);
}
@NonNull
static MarqueeParameters fromProto(@NonNull LayoutElementProto.MarqueeParameters proto) {
return fromProto(proto, null);
}
/** Returns the internal proto instance. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.MarqueeParameters toProto() {
return mImpl;
}
@Override
@NonNull
public String toString() {
return "MarqueeParameters{" + "iterations=" + getIterations() + "}";
}
/** Builder for {@link MarqueeParameters} */
public static final class Builder {
private final LayoutElementProto.MarqueeParameters.Builder mImpl =
LayoutElementProto.MarqueeParameters.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(1405971293);
public Builder() {}
/**
* Sets the number of times to repeat the Marquee animation. Set to -1 to repeat
* indefinitely. Defaults to repeat indefinitely.
*
* @since 1.2
*/
@ProtoLayoutExperimental
@NonNull
public Builder setIterations(int iterations) {
mImpl.setIterations(iterations);
mFingerprint.recordPropertyUpdate(1, iterations);
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public MarqueeParameters build() {
return new MarqueeParameters(mImpl.build(), mFingerprint);
}
}
}
/**
* An Android platform specific text style configuration options for styling and compatibility.
*
* @since 1.2
*/
@ProtoLayoutExperimental
public static final class AndroidTextStyle {
private final LayoutElementProto.AndroidTextStyle mImpl;
@Nullable private final Fingerprint mFingerprint;
AndroidTextStyle(LayoutElementProto.AndroidTextStyle impl,
@Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets whether the {@link Text} excludes padding specified by the font, i.e. extra top and
* bottom padding above the normal ascent and descent. The default is false.
*
* @since 1.2
*/
public boolean getExcludeFontPadding() {
return mImpl.getExcludeFontPadding();
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
/** Creates a new wrapper instance from the proto. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public static AndroidTextStyle fromProto(
@NonNull LayoutElementProto.AndroidTextStyle proto,
@Nullable Fingerprint fingerprint) {
return new AndroidTextStyle(proto, fingerprint);
}
@NonNull
static AndroidTextStyle fromProto(@NonNull LayoutElementProto.AndroidTextStyle proto) {
return fromProto(proto, null);
}
/** Returns the internal proto instance. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.AndroidTextStyle toProto() {
return mImpl;
}
@Override
@NonNull
public String toString() {
return "AndroidTextStyle{" + "excludeFontPadding=" + getExcludeFontPadding() + "}";
}
/** Builder for {@link AndroidTextStyle} */
public static final class Builder {
private final LayoutElementProto.AndroidTextStyle.Builder mImpl =
LayoutElementProto.AndroidTextStyle.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(408674745);
public Builder() {}
/**
* Sets whether the {@link Text} excludes padding specified by the font, i.e. extra
* top and bottom padding above the normal ascent and descent. The default is false.
*
* @since 1.2
*/
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setExcludeFontPadding(boolean excludeFontPadding) {
mImpl.setExcludeFontPadding(excludeFontPadding);
mFingerprint.recordPropertyUpdate(1, Boolean.hashCode(excludeFontPadding));
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public AndroidTextStyle build() {
return new AndroidTextStyle(mImpl.build(), mFingerprint);
}
}
}
/** A text string. */
public static final class Text implements LayoutElement {
private final LayoutElementProto.Text mImpl;
@Nullable private final Fingerprint mFingerprint;
Text(LayoutElementProto.Text impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the text to render. Intended for testing purposes only. */
@Nullable
public StringProp getText() {
if (mImpl.hasText()) {
return StringProp.fromProto(mImpl.getText());
} else {
return null;
}
}
/**
* Gets the bounding constraints for the layout affected by the dynamic value from {@link
* #getText()}.
*
* @since 1.2
*/
@Nullable
public StringLayoutConstraint getLayoutConstraintsForDynamicText() {
if (mImpl.hasText()) {
return StringLayoutConstraint.fromProto(mImpl.getText());
} else {
return null;
}
}
/**
* Gets the style of font to use (size, bold etc). If not specified, defaults to the
* platform's default body font. Intended for testing purposes only.
*/
@Nullable
public FontStyle getFontStyle() {
if (mImpl.hasFontStyle()) {
return FontStyle.fromProto(mImpl.getFontStyle());
} else {
return null;
}
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public Modifiers getModifiers() {
if (mImpl.hasModifiers()) {
return Modifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
/**
* Gets the maximum number of lines that can be represented by the {@link Text} element. If
* not defined, the {@link Text} element will be treated as a single-line element. Intended
* for testing purposes only.
*/
@Nullable
public Int32Prop getMaxLines() {
if (mImpl.hasMaxLines()) {
return Int32Prop.fromProto(mImpl.getMaxLines());
} else {
return null;
}
}
/**
* Gets alignment of the text within its bounds. Note that a {@link Text} element will size
* itself to wrap its contents, so this option is meaningless for single-line text (for
* that, use alignment of the outer container). For multi-line text, however, this will set
* the alignment of lines relative to the {@link Text} element bounds. If not defined,
* defaults to TEXT_ALIGN_CENTER. Intended for testing purposes only.
*/
@Nullable
public TextAlignmentProp getMultilineAlignment() {
if (mImpl.hasMultilineAlignment()) {
return TextAlignmentProp.fromProto(mImpl.getMultilineAlignment());
} else {
return null;
}
}
/**
* Gets how to handle text which overflows the bound of the {@link Text} element. A {@link
* Text} element will grow as large as possible inside its parent container (while still
* respecting max_lines); if it cannot grow large enough to render all of its text, the text
* which cannot fit inside its container will be truncated. If not defined, defaults to
* TEXT_OVERFLOW_TRUNCATE. Intended for testing purposes only.
*/
@Nullable
public TextOverflowProp getOverflow() {
if (mImpl.hasOverflow()) {
return TextOverflowProp.fromProto(mImpl.getOverflow());
} else {
return null;
}
}
/**
* Gets the explicit height between lines of text. This is equivalent to the vertical
* distance between subsequent baselines. If not specified, defaults the font's recommended
* interline spacing. Intended for testing purposes only.
*/
@Nullable
public SpProp getLineHeight() {
if (mImpl.hasLineHeight()) {
return SpProp.fromProto(mImpl.getLineHeight());
} else {
return null;
}
}
/**
* Gets an Android platform specific text style configuration options for styling and
* compatibility.
*
* @since 1.2
*/
@ProtoLayoutExperimental
@Nullable
public AndroidTextStyle getAndroidTextStyle() {
if (mImpl.hasAndroidTextStyle()) {
return AndroidTextStyle.fromProto(mImpl.getAndroidTextStyle());
} else {
return null;
}
}
/**
* Gets the number of times to repeat the Marquee animation. Only applies when overflow is
* TEXT_OVERFLOW_MARQUEE. Set to -1 to repeat indefinitely. Defaults to repeat indefinitely.
*
* @since 1.2
*/
@ProtoLayoutExperimental
@IntRange(from = -1)
public int getMarqueeIterations() {
return mImpl.getMarqueeParameters().getIterations();
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static Text fromProto(@NonNull LayoutElementProto.Text proto) {
return new Text(proto, null);
}
@NonNull
LayoutElementProto.Text toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.LayoutElement toLayoutElementProto() {
return LayoutElementProto.LayoutElement.newBuilder().setText(mImpl).build();
}
/** Builder for {@link Text}. */
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Text.Builder mImpl =
LayoutElementProto.Text.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(1976530157);
public Builder() {}
/**
* Sets the text to render.
*
* <p>This field is made bindable from version 1.2 and will use the dynamic value (if
* set). Older renderers will still consider this field as non-bindable and will use the
* static value.
*
* <p>When using a dynamic value, make sure to specify the bounding constraints for the
* affected layout element through {@link
* #setLayoutConstraintsForDynamicText(StringLayoutConstraint)} otherwise {@link
* #build()} fails.
*
* @since 1.0
*/
@NonNull
public Builder setText(@NonNull StringProp text) {
mImpl.mergeText(text.toProto());
mFingerprint.recordPropertyUpdate(
1, checkNotNull(text.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the bounding constraints for the layout affected by the dynamic value from
* {@link #setText(StringProp)}}.
*
* @since 1.2
*/
@NonNull
public Builder setLayoutConstraintsForDynamicText(
@NonNull StringLayoutConstraint stringLayoutConstraint) {
mImpl.mergeText(stringLayoutConstraint.toProto());
mFingerprint.recordPropertyUpdate(
1,
checkNotNull(stringLayoutConstraint.getFingerprint())
.aggregateValueAsInt());
return this;
}
/**
* Sets the static text to render.
*
* @since 1.0
*/
@NonNull
public Builder setText(@NonNull String text) {
mImpl.setText(TypesProto.StringProp.newBuilder().setValue(text));
return this;
}
/**
* Sets the style of font to use (size, bold etc). If not specified, defaults to the
* platform's default body font.
*/
@NonNull
public Builder setFontStyle(@NonNull FontStyle fontStyle) {
mImpl.setFontStyle(fontStyle.toProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(fontStyle.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull Modifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the maximum number of lines that can be represented by the {@link Text} element.
* If not defined, the {@link Text} element will be treated as a single-line element.
*/
@NonNull
public Builder setMaxLines(@NonNull Int32Prop maxLines) {
mImpl.setMaxLines(maxLines.toProto());
mFingerprint.recordPropertyUpdate(
4, checkNotNull(maxLines.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the maximum number of lines that can be represented by the {@link Text} element.
* If not defined, the {@link Text} element will be treated as a single-line element.
*/
@NonNull
public Builder setMaxLines(@IntRange(from = 1) int maxLines) {
mImpl.setMaxLines(TypesProto.Int32Prop.newBuilder().setValue(maxLines));
return this;
}
/**
* Sets alignment of the text within its bounds. Note that a {@link Text} element will
* size itself to wrap its contents, so this option is meaningless for single-line text
* (for that, use alignment of the outer container). For multi-line text, however, this
* will set the alignment of lines relative to the {@link Text} element bounds. If not
* defined, defaults to TEXT_ALIGN_CENTER.
*/
@NonNull
public Builder setMultilineAlignment(@NonNull TextAlignmentProp multilineAlignment) {
mImpl.setMultilineAlignment(multilineAlignment.toProto());
mFingerprint.recordPropertyUpdate(
5, checkNotNull(multilineAlignment.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets alignment of the text within its bounds. Note that a {@link Text} element will
* size itself to wrap its contents, so this option is meaningless for single-line text
* (for that, use alignment of the outer container). For multi-line text, however, this
* will set the alignment of lines relative to the {@link Text} element bounds. If not
* defined, defaults to TEXT_ALIGN_CENTER.
*/
@NonNull
public Builder setMultilineAlignment(@TextAlignment int multilineAlignment) {
mImpl.setMultilineAlignment(
AlignmentProto.TextAlignmentProp.newBuilder()
.setValue(
AlignmentProto.TextAlignment.forNumber(
multilineAlignment)));
return this;
}
/**
* Sets how to handle text which overflows the bound of the {@link Text} element. A
* {@link Text} element will grow as large as possible inside its parent container
* (while still respecting max_lines); if it cannot grow large enough to render all of
* its text, the text which cannot fit inside its container will be truncated. If not
* defined, defaults to TEXT_OVERFLOW_TRUNCATE.
*/
@NonNull
public Builder setOverflow(@NonNull TextOverflowProp overflow) {
mImpl.setOverflow(overflow.toProto());
mFingerprint.recordPropertyUpdate(
6, checkNotNull(overflow.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets how to handle text which overflows the bound of the {@link Text} element. A
* {@link Text} element will grow as large as possible inside its parent container
* (while still respecting max_lines); if it cannot grow large enough to render all of
* its text, the text which cannot fit inside its container will be truncated. If not
* defined, defaults to TEXT_OVERFLOW_TRUNCATE.
*/
@NonNull
public Builder setOverflow(@TextOverflow int overflow) {
mImpl.setOverflow(
LayoutElementProto.TextOverflowProp.newBuilder()
.setValue(LayoutElementProto.TextOverflow.forNumber(overflow)));
return this;
}
/**
* Sets the explicit height between lines of text. This is equivalent to the vertical
* distance between subsequent baselines. If not specified, defaults the font's
* recommended interline spacing.
*/
@NonNull
public Builder setLineHeight(@NonNull SpProp lineHeight) {
mImpl.setLineHeight(lineHeight.toProto());
mFingerprint.recordPropertyUpdate(
7, checkNotNull(lineHeight.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets an Android platform specific text style configuration options for styling and
* compatibility.
*
* @since 1.2
*/
@ProtoLayoutExperimental
@NonNull
public Builder setAndroidTextStyle(@NonNull AndroidTextStyle androidTextStyle) {
mImpl.setAndroidTextStyle(androidTextStyle.toProto());
mFingerprint.recordPropertyUpdate(
8, checkNotNull(androidTextStyle.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the number of times to repeat the Marquee animation. Only applies when overflow
* is TEXT_OVERFLOW_MARQUEE. Set to -1 to repeat indefinitely. Defaults to repeat
* indefinitely.
*
* @since 1.2
*/
@ProtoLayoutExperimental
@NonNull
public Builder setMarqueeIterations(@IntRange(from = -1) int marqueeIterations) {
mImpl.setMarqueeParameters(
LayoutElementProto.MarqueeParameters.newBuilder()
.setIterations(marqueeIterations));
mFingerprint.recordPropertyUpdate(9, marqueeIterations);
return this;
}
@Override
@NonNull
public Text build() {
TypesProto.StringProp text = mImpl.getText();
if (text.hasDynamicValue() && !text.hasValueForLayout()) {
throw new IllegalStateException(
"text with dynamic value requires "
+ "layoutConstraintsForDynamicText to be present.");
}
return new Text(mImpl.build(), mFingerprint);
}
}
}
/** An extensible {@code ContentScaleMode} property. */
public static final class ContentScaleModeProp {
private final LayoutElementProto.ContentScaleModeProp mImpl;
@Nullable private final Fingerprint mFingerprint;
ContentScaleModeProp(
LayoutElementProto.ContentScaleModeProp impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the value. Intended for testing purposes only. */
@ContentScaleMode
public int getValue() {
return mImpl.getValue().getNumber();
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static ContentScaleModeProp fromProto(
@NonNull LayoutElementProto.ContentScaleModeProp proto) {
return new ContentScaleModeProp(proto, null);
}
@NonNull
LayoutElementProto.ContentScaleModeProp toProto() {
return mImpl;
}
/** Builder for {@link ContentScaleModeProp} */
public static final class Builder {
private final LayoutElementProto.ContentScaleModeProp.Builder mImpl =
LayoutElementProto.ContentScaleModeProp.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-893830536);
public Builder() {}
/** Sets the value. */
@NonNull
public Builder setValue(@ContentScaleMode int value) {
mImpl.setValue(LayoutElementProto.ContentScaleMode.forNumber(value));
mFingerprint.recordPropertyUpdate(1, value);
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public ContentScaleModeProp build() {
return new ContentScaleModeProp(mImpl.build(), mFingerprint);
}
}
}
/** Filtering parameters used for images. This can be used to apply a color tint to images. */
public static final class ColorFilter {
private final LayoutElementProto.ColorFilter mImpl;
@Nullable private final Fingerprint mFingerprint;
ColorFilter(LayoutElementProto.ColorFilter impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets the tint color to use. If specified, the image will be tinted, using SRC_IN blending
* (that is, all color information will be stripped from the target image, and only the
* alpha channel will be blended with the requested color).
*
* <p>Note that only Android image resources can be tinted; Inline images will not be
* tinted, and this property will have no effect. Intended for testing purposes only.
*/
@Nullable
public ColorProp getTint() {
if (mImpl.hasTint()) {
return ColorProp.fromProto(mImpl.getTint());
} else {
return null;
}
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static ColorFilter fromProto(@NonNull LayoutElementProto.ColorFilter proto) {
return new ColorFilter(proto, null);
}
@NonNull
LayoutElementProto.ColorFilter toProto() {
return mImpl;
}
/** Builder for {@link ColorFilter} */
public static final class Builder {
private final LayoutElementProto.ColorFilter.Builder mImpl =
LayoutElementProto.ColorFilter.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(181311326);
public Builder() {}
/**
* Sets the tint color to use. If specified, the image will be tinted, using SRC_IN
* blending (that is, all color information will be stripped from the target image, and
* only the alpha channel will be blended with the requested color).
*
* <p>Note that only Android image resources can be tinted; Inline images will not be
* tinted, and this property will have no effect.
*
* <p>This field is made bindable from version 1.2 and will use the dynamic value (if
* set). Older renderers will still consider this field as non-bindable and will use the
* static value.
*/
@NonNull
public Builder setTint(@NonNull ColorProp tint) {
mImpl.setTint(tint.toProto());
mFingerprint.recordPropertyUpdate(
1, checkNotNull(tint.getFingerprint()).aggregateValueAsInt());
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public ColorFilter build() {
return new ColorFilter(mImpl.build(), mFingerprint);
}
}
}
/**
* An image.
*
* <p>Images used in this element must exist in the resource bundle that corresponds to this
* layout. Images must have their dimension specified, and will be rendered at this width and
* height, regardless of their native dimension.
*/
public static final class Image implements LayoutElement {
private final LayoutElementProto.Image mImpl;
@Nullable private final Fingerprint mFingerprint;
Image(LayoutElementProto.Image impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets the resource_id of the image to render. This must exist in the supplied resource
* bundle. Intended for testing purposes only.
*/
@Nullable
public StringProp getResourceId() {
if (mImpl.hasResourceId()) {
return StringProp.fromProto(mImpl.getResourceId());
} else {
return null;
}
}
/**
* Gets the width of this image. If not defined, the image will not be rendered. Intended
* for testing purposes only.
*/
@Nullable
public ImageDimension getWidth() {
if (mImpl.hasWidth()) {
return DimensionBuilders.imageDimensionFromProto(mImpl.getWidth());
} else {
return null;
}
}
/**
* Gets the height of this image. If not defined, the image will not be rendered. Intended
* for testing purposes only.
*/
@Nullable
public ImageDimension getHeight() {
if (mImpl.hasHeight()) {
return DimensionBuilders.imageDimensionFromProto(mImpl.getHeight());
} else {
return null;
}
}
/**
* Gets how to scale the image resource inside the bounds specified by width/height if its
* size does not match those bounds. Defaults to CONTENT_SCALE_MODE_FIT. Intended for
* testing purposes only.
*/
@Nullable
public ContentScaleModeProp getContentScaleMode() {
if (mImpl.hasContentScaleMode()) {
return ContentScaleModeProp.fromProto(mImpl.getContentScaleMode());
} else {
return null;
}
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public Modifiers getModifiers() {
if (mImpl.hasModifiers()) {
return Modifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
/**
* Gets filtering parameters for this image. If not specified, defaults to no filtering.
* Intended for testing purposes only.
*/
@Nullable
public ColorFilter getColorFilter() {
if (mImpl.hasColorFilter()) {
return ColorFilter.fromProto(mImpl.getColorFilter());
} else {
return null;
}
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static Image fromProto(@NonNull LayoutElementProto.Image proto) {
return new Image(proto, null);
}
@NonNull
LayoutElementProto.Image toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.LayoutElement toLayoutElementProto() {
return LayoutElementProto.LayoutElement.newBuilder().setImage(mImpl).build();
}
/** Builder for {@link Image}. */
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Image.Builder mImpl =
LayoutElementProto.Image.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-543078544);
public Builder() {}
/**
* Sets the resource_id of the image to render. This must exist in the supplied resource
* bundle.
*/
@NonNull
public Builder setResourceId(@NonNull StringProp resourceId) {
mImpl.setResourceId(resourceId.toProto());
mFingerprint.recordPropertyUpdate(
1, checkNotNull(resourceId.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the resource_id of the image to render. This must exist in the supplied resource
* bundle.
*/
@NonNull
public Builder setResourceId(@NonNull String resourceId) {
mImpl.setResourceId(TypesProto.StringProp.newBuilder().setValue(resourceId));
return this;
}
/** Sets the width of this image. If not defined, the image will not be rendered. */
@NonNull
public Builder setWidth(@NonNull ImageDimension width) {
mImpl.setWidth(width.toImageDimensionProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
return this;
}
/** Sets the height of this image. If not defined, the image will not be rendered. */
@NonNull
public Builder setHeight(@NonNull ImageDimension height) {
mImpl.setHeight(height.toImageDimensionProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets how to scale the image resource inside the bounds specified by width/height if
* its size does not match those bounds. Defaults to CONTENT_SCALE_MODE_FIT.
*/
@NonNull
public Builder setContentScaleMode(@NonNull ContentScaleModeProp contentScaleMode) {
mImpl.setContentScaleMode(contentScaleMode.toProto());
mFingerprint.recordPropertyUpdate(
4, checkNotNull(contentScaleMode.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets how to scale the image resource inside the bounds specified by width/height if
* its size does not match those bounds. Defaults to CONTENT_SCALE_MODE_FIT.
*/
@NonNull
public Builder setContentScaleMode(@ContentScaleMode int contentScaleMode) {
mImpl.setContentScaleMode(
LayoutElementProto.ContentScaleModeProp.newBuilder()
.setValue(
LayoutElementProto.ContentScaleMode.forNumber(
contentScaleMode)));
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull Modifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
5, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets filtering parameters for this image. If not specified, defaults to no filtering.
*/
@NonNull
public Builder setColorFilter(@NonNull ColorFilter colorFilter) {
mImpl.setColorFilter(colorFilter.toProto());
mFingerprint.recordPropertyUpdate(
6, checkNotNull(colorFilter.getFingerprint()).aggregateValueAsInt());
return this;
}
@Override
@NonNull
public Image build() {
return new Image(mImpl.build(), mFingerprint);
}
}
}
/** A simple spacer, typically used to provide padding between adjacent elements. */
public static final class Spacer implements LayoutElement {
private final LayoutElementProto.Spacer mImpl;
@Nullable private final Fingerprint mFingerprint;
Spacer(LayoutElementProto.Spacer impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets the width of this {@link Spacer}. When this is added as the direct child of an
* {@link Arc}, this must be specified as an angular dimension, otherwise a linear dimension
* must be used. If not defined, defaults to 0.
*/
@Nullable
public SpacerDimension getWidth() {
if (mImpl.hasWidth()) {
return DimensionBuilders.spacerDimensionFromProto(mImpl.getWidth());
} else {
return null;
}
}
/**
* Gets the bounding constraints for the layout affected by the dynamic value from {@link
* #getWidth()}.
*
* @since 1.2
*/
@Nullable
public HorizontalLayoutConstraint getLayoutConstraintsForDynamicWidth() {
if (mImpl.getWidth().hasLinearDimension()) {
return HorizontalLayoutConstraint.fromProto(mImpl.getWidth().getLinearDimension());
} else {
return null;
}
}
/** Gets the height of this spacer. If not defined, defaults to 0. */
@Nullable
public SpacerDimension getHeight() {
if (mImpl.hasHeight()) {
return DimensionBuilders.spacerDimensionFromProto(mImpl.getHeight());
} else {
return null;
}
}
/**
* Gets the bounding constraints for the layout affected by the dynamic value from {@link
* #getHeight()}.
*
* @since 1.2
*/
@Nullable
public VerticalLayoutConstraint getLayoutConstraintsForDynamicHeight() {
if (mImpl.getHeight().hasLinearDimension()) {
return VerticalLayoutConstraint.fromProto(mImpl.getHeight().getLinearDimension());
} else {
return null;
}
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public Modifiers getModifiers() {
if (mImpl.hasModifiers()) {
return Modifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static Spacer fromProto(@NonNull LayoutElementProto.Spacer proto) {
return new Spacer(proto, null);
}
@NonNull
LayoutElementProto.Spacer toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.LayoutElement toLayoutElementProto() {
return LayoutElementProto.LayoutElement.newBuilder().setSpacer(mImpl).build();
}
/** Builder for {@link Spacer}. */
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Spacer.Builder mImpl =
LayoutElementProto.Spacer.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-1748084575);
public Builder() {}
/**
* Sets the width of this {@link Spacer}. When this is added as the direct child of an
* {@link Arc}, this must be specified as an angular dimension, otherwise a linear
* dimension must be used. If not defined, defaults to 0.
*
* <p>This field is made bindable from version 1.2 and will use the dynamic value (if
* set). Older renderers will still consider this field as non-bindable and will use the
* static value.
*
* <p>When using a dynamic value, make sure to specify the bounding constraints for the
* affected layout element through {@link
* #setLayoutConstraintsForDynamicWidth(HorizontalLayoutConstraint)} otherwise {@link
* #build()} fails.
*/
@NonNull
public Builder setWidth(@NonNull SpacerDimension width) {
mImpl.mergeWidth(width.toSpacerDimensionProto());
mFingerprint.recordPropertyUpdate(
1, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the bounding constraints for the layout affected by the dynamic value from
* {@link #setWidth(SpacerDimension)}. If the {@link SpacerDimension} does not have a
* dynamic value, this will be ignored.
*
* @since 1.2
*/
@NonNull
public Builder setLayoutConstraintsForDynamicWidth(
@NonNull HorizontalLayoutConstraint horizontalLayoutConstraint) {
switch (mImpl.getWidth().getInnerCase()) {
case INNER_NOT_SET:
case LINEAR_DIMENSION:
mImpl.mergeWidth(horizontalLayoutConstraint.toSpacerDimensionProto());
mFingerprint.recordPropertyUpdate(
1,
checkNotNull(horizontalLayoutConstraint.getFingerprint())
.aggregateValueAsInt());
break;
default:
}
return this;
}
/**
* Sets the height of this spacer. If not defined, defaults to 0.
*
* <p>This field is made bindable from version 1.2 and will use the dynamic value (if
* set). Older renderers will still consider this field as non-bindable and will use the
* static value.
*
* <p>When using a dynamic value, make sure to specify the bounding constraints for the
* affected layout element through {@link
* #setLayoutConstraintsForDynamicWidth(HorizontalLayoutConstraint)} otherwise {@link
* #build()} fails.
*
* @since 1.0
*/
@NonNull
public Builder setHeight(@NonNull SpacerDimension height) {
mImpl.setHeight(height.toSpacerDimensionProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the bounding constraints for the layout affected by the dynamic value from
* {@link #setHeight(SpacerDimension)}. If the {@link SpacerDimension} does not have a
* dynamic value, this will be ignored.
*
* @since 1.2
*/
@NonNull
public Builder setLayoutConstraintsForDynamicHeight(
@NonNull VerticalLayoutConstraint verticalLayoutConstraint) {
switch (mImpl.getHeight().getInnerCase()) {
case INNER_NOT_SET:
case LINEAR_DIMENSION:
mImpl.mergeHeight(verticalLayoutConstraint.toSpacerDimensionProto());
mFingerprint.recordPropertyUpdate(
2,
checkNotNull(verticalLayoutConstraint.getFingerprint())
.aggregateValueAsInt());
break;
default:
}
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull Modifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
@Override
@NonNull
public Spacer build() {
DimensionProto.DpProp width = mImpl.getWidth().getLinearDimension();
if (width.hasDynamicValue() && !width.hasValueForLayout()) {
throw new IllegalStateException(
"width with dynamic value requires "
+ "layoutConstraintsForDynamicWidth to be present.");
}
DimensionProto.DpProp height = mImpl.getHeight().getLinearDimension();
if (height.hasDynamicValue() && !height.hasValueForLayout()) {
throw new IllegalStateException(
"height with dynamic value requires "
+ "layoutConstraintsForDynamicHeight to be present.");
}
return new Spacer(mImpl.build(), mFingerprint);
}
}
}
/**
* A container which stacks all of its children on top of one another. This also allows to add a
* background color, or to have a border around them with some padding.
*/
public static final class Box implements LayoutElement {
private final LayoutElementProto.Box mImpl;
@Nullable private final Fingerprint mFingerprint;
Box(LayoutElementProto.Box impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the child element(s) to wrap. Intended for testing purposes only. */
@NonNull
public List<LayoutElement> getContents() {
List<LayoutElement> list = new ArrayList<>();
for (LayoutElementProto.LayoutElement item : mImpl.getContentsList()) {
list.add(LayoutElementBuilders.layoutElementFromProto(item));
}
return Collections.unmodifiableList(list);
}
/**
* Gets the height of this {@link Box}. If not defined, this will size itself to fit all of
* its children (i.e. a WrappedDimension). Intended for testing purposes only.
*/
@Nullable
public ContainerDimension getHeight() {
if (mImpl.hasHeight()) {
return DimensionBuilders.containerDimensionFromProto(mImpl.getHeight());
} else {
return null;
}
}
/**
* Gets the width of this {@link Box}. If not defined, this will size itself to fit all of
* its children (i.e. a WrappedDimension). Intended for testing purposes only.
*/
@Nullable
public ContainerDimension getWidth() {
if (mImpl.hasWidth()) {
return DimensionBuilders.containerDimensionFromProto(mImpl.getWidth());
} else {
return null;
}
}
/**
* Gets the horizontal alignment of the element inside this {@link Box}. If not defined,
* defaults to HORIZONTAL_ALIGN_CENTER. Intended for testing purposes only.
*/
@Nullable
public HorizontalAlignmentProp getHorizontalAlignment() {
if (mImpl.hasHorizontalAlignment()) {
return HorizontalAlignmentProp.fromProto(mImpl.getHorizontalAlignment());
} else {
return null;
}
}
/**
* Gets the vertical alignment of the element inside this {@link Box}. If not defined,
* defaults to VERTICAL_ALIGN_CENTER. Intended for testing purposes only.
*/
@Nullable
public VerticalAlignmentProp getVerticalAlignment() {
if (mImpl.hasVerticalAlignment()) {
return VerticalAlignmentProp.fromProto(mImpl.getVerticalAlignment());
} else {
return null;
}
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public Modifiers getModifiers() {
if (mImpl.hasModifiers()) {
return Modifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static Box fromProto(@NonNull LayoutElementProto.Box proto) {
return new Box(proto, null);
}
@NonNull
LayoutElementProto.Box toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.LayoutElement toLayoutElementProto() {
return LayoutElementProto.LayoutElement.newBuilder().setBox(mImpl).build();
}
/** Builder for {@link Box}. */
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Box.Builder mImpl =
LayoutElementProto.Box.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-1881256071);
public Builder() {}
/** Adds one item to the child element(s) to wrap. */
@NonNull
public Builder addContent(@NonNull LayoutElement content) {
mImpl.addContents(content.toLayoutElementProto());
mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
return this;
}
/**
* Sets the height of this {@link Box}. If not defined, this will size itself to fit all
* of its children (i.e. a WrappedDimension).
*/
@NonNull
public Builder setHeight(@NonNull ContainerDimension height) {
mImpl.setHeight(height.toContainerDimensionProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the width of this {@link Box}. If not defined, this will size itself to fit all
* of its children (i.e. a WrappedDimension).
*/
@NonNull
public Builder setWidth(@NonNull ContainerDimension width) {
mImpl.setWidth(width.toContainerDimensionProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the horizontal alignment of the element inside this {@link Box}. If not defined,
* defaults to HORIZONTAL_ALIGN_CENTER.
*/
@NonNull
public Builder setHorizontalAlignment(
@NonNull HorizontalAlignmentProp horizontalAlignment) {
mImpl.setHorizontalAlignment(horizontalAlignment.toProto());
mFingerprint.recordPropertyUpdate(
4,
checkNotNull(horizontalAlignment.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the horizontal alignment of the element inside this {@link Box}. If not defined,
* defaults to HORIZONTAL_ALIGN_CENTER.
*/
@NonNull
public Builder setHorizontalAlignment(@HorizontalAlignment int horizontalAlignment) {
mImpl.setHorizontalAlignment(
AlignmentProto.HorizontalAlignmentProp.newBuilder()
.setValue(
AlignmentProto.HorizontalAlignment.forNumber(
horizontalAlignment)));
return this;
}
/**
* Sets the vertical alignment of the element inside this {@link Box}. If not defined,
* defaults to VERTICAL_ALIGN_CENTER.
*/
@NonNull
public Builder setVerticalAlignment(@NonNull VerticalAlignmentProp verticalAlignment) {
mImpl.setVerticalAlignment(verticalAlignment.toProto());
mFingerprint.recordPropertyUpdate(
5, checkNotNull(verticalAlignment.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the vertical alignment of the element inside this {@link Box}. If not defined,
* defaults to VERTICAL_ALIGN_CENTER.
*/
@NonNull
public Builder setVerticalAlignment(@VerticalAlignment int verticalAlignment) {
mImpl.setVerticalAlignment(
AlignmentProto.VerticalAlignmentProp.newBuilder()
.setValue(
AlignmentProto.VerticalAlignment.forNumber(
verticalAlignment)));
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull Modifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
6, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
@Override
@NonNull
public Box build() {
return new Box(mImpl.build(), mFingerprint);
}
}
}
/**
* A portion of text which can be added to a {@link Span}. Two different {@link SpanText}
* elements on the same line will be aligned to the same baseline, regardless of the size of
* each {@link SpanText}.
*/
public static final class SpanText implements Span {
private final LayoutElementProto.SpanText mImpl;
@Nullable private final Fingerprint mFingerprint;
SpanText(LayoutElementProto.SpanText impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the text to render. Intended for testing purposes only. */
@Nullable
public StringProp getText() {
if (mImpl.hasText()) {
return StringProp.fromProto(mImpl.getText());
} else {
return null;
}
}
/**
* Gets the style of font to use (size, bold etc). If not specified, defaults to the
* platform's default body font. Intended for testing purposes only.
*/
@Nullable
public FontStyle getFontStyle() {
if (mImpl.hasFontStyle()) {
return FontStyle.fromProto(mImpl.getFontStyle());
} else {
return null;
}
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public SpanModifiers getModifiers() {
if (mImpl.hasModifiers()) {
return SpanModifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
/**
* Gets an Android platform specific text style configuration options for styling and
* compatibility.
*
* @since 1.2
*/
@ProtoLayoutExperimental
@Nullable
public AndroidTextStyle getAndroidTextStyle() {
if (mImpl.hasAndroidTextStyle()) {
return AndroidTextStyle.fromProto(mImpl.getAndroidTextStyle());
} else {
return null;
}
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static SpanText fromProto(@NonNull LayoutElementProto.SpanText proto) {
return new SpanText(proto, null);
}
@NonNull
LayoutElementProto.SpanText toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.Span toSpanProto() {
return LayoutElementProto.Span.newBuilder().setText(mImpl).build();
}
/** Builder for {@link SpanText}. */
public static final class Builder implements Span.Builder {
private final LayoutElementProto.SpanText.Builder mImpl =
LayoutElementProto.SpanText.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-221774557);
public Builder() {}
/**
* Sets the text to render.
*
* <p>Note that this field only supports static values.
*
* @since 1.0
*/
@NonNull
public Builder setText(@NonNull StringProp text) {
if (text.getDynamicValue() != null) {
throw new IllegalArgumentException(
"SpanText.Builder.setText doesn't support dynamic values.");
}
mImpl.setText(text.toProto());
mFingerprint.recordPropertyUpdate(
1, checkNotNull(text.getFingerprint()).aggregateValueAsInt());
return this;
}
/** Sets the text to render. */
@NonNull
public Builder setText(@NonNull String text) {
mImpl.setText(TypesProto.StringProp.newBuilder().setValue(text));
return this;
}
/**
* Sets the style of font to use (size, bold etc). If not specified, defaults to the
* platform's default body font.
*/
@NonNull
public Builder setFontStyle(@NonNull FontStyle fontStyle) {
mImpl.setFontStyle(fontStyle.toProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(fontStyle.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull SpanModifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets an Android platform specific text style configuration options for styling and
* compatibility.
*
* @since 1.2
*/
@ProtoLayoutExperimental
@NonNull
public Builder setAndroidTextStyle(@NonNull AndroidTextStyle androidTextStyle) {
mImpl.setAndroidTextStyle(androidTextStyle.toProto());
mFingerprint.recordPropertyUpdate(
4, checkNotNull(androidTextStyle.getFingerprint()).aggregateValueAsInt());
return this;
}
@Override
@NonNull
public SpanText build() {
return new SpanText(mImpl.build(), mFingerprint);
}
}
}
/** An image which can be added to a {@link Span}. */
public static final class SpanImage implements Span {
private final LayoutElementProto.SpanImage mImpl;
@Nullable private final Fingerprint mFingerprint;
SpanImage(LayoutElementProto.SpanImage impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets the resource_id of the image to render. This must exist in the supplied resource
* bundle. Intended for testing purposes only.
*/
@Nullable
public StringProp getResourceId() {
if (mImpl.hasResourceId()) {
return StringProp.fromProto(mImpl.getResourceId());
} else {
return null;
}
}
/**
* Gets the width of this image. If not defined, the image will not be rendered. Intended
* for testing purposes only.
*/
@Nullable
public DpProp getWidth() {
if (mImpl.hasWidth()) {
return DpProp.fromProto(mImpl.getWidth());
} else {
return null;
}
}
/**
* Gets the height of this image. If not defined, the image will not be rendered. Intended
* for testing purposes only.
*/
@Nullable
public DpProp getHeight() {
if (mImpl.hasHeight()) {
return DpProp.fromProto(mImpl.getHeight());
} else {
return null;
}
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public SpanModifiers getModifiers() {
if (mImpl.hasModifiers()) {
return SpanModifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
/**
* Gets alignment of this image within the line height of the surrounding {@link Spannable}.
* If undefined, defaults to SPAN_VERTICAL_ALIGN_BOTTOM. Intended for testing purposes only.
*/
@Nullable
public SpanVerticalAlignmentProp getAlignment() {
if (mImpl.hasAlignment()) {
return SpanVerticalAlignmentProp.fromProto(mImpl.getAlignment());
} else {
return null;
}
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static SpanImage fromProto(@NonNull LayoutElementProto.SpanImage proto) {
return new SpanImage(proto, null);
}
@NonNull
LayoutElementProto.SpanImage toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.Span toSpanProto() {
return LayoutElementProto.Span.newBuilder().setImage(mImpl).build();
}
/** Builder for {@link SpanImage}. */
public static final class Builder implements Span.Builder {
private final LayoutElementProto.SpanImage.Builder mImpl =
LayoutElementProto.SpanImage.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(502289772);
public Builder() {}
/**
* Sets the resource_id of the image to render. This must exist in the supplied resource
* bundle.
*/
@NonNull
public Builder setResourceId(@NonNull StringProp resourceId) {
mImpl.setResourceId(resourceId.toProto());
mFingerprint.recordPropertyUpdate(
1, checkNotNull(resourceId.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the resource_id of the image to render. This must exist in the supplied resource
* bundle.
*/
@NonNull
public Builder setResourceId(@NonNull String resourceId) {
mImpl.setResourceId(TypesProto.StringProp.newBuilder().setValue(resourceId));
return this;
}
/** Sets the width of this image. If not defined, the image will not be rendered. */
@NonNull
public Builder setWidth(@NonNull DpProp width) {
mImpl.setWidth(width.toProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
return this;
}
/** Sets the height of this image. If not defined, the image will not be rendered. */
@NonNull
public Builder setHeight(@NonNull DpProp height) {
mImpl.setHeight(height.toProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull SpanModifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
4, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets alignment of this image within the line height of the surrounding {@link
* Spannable}. If undefined, defaults to SPAN_VERTICAL_ALIGN_BOTTOM.
*/
@NonNull
public Builder setAlignment(@NonNull SpanVerticalAlignmentProp alignment) {
mImpl.setAlignment(alignment.toProto());
mFingerprint.recordPropertyUpdate(
5, checkNotNull(alignment.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets alignment of this image within the line height of the surrounding {@link
* Spannable}. If undefined, defaults to SPAN_VERTICAL_ALIGN_BOTTOM.
*/
@NonNull
public Builder setAlignment(@SpanVerticalAlignment int alignment) {
mImpl.setAlignment(
LayoutElementProto.SpanVerticalAlignmentProp.newBuilder()
.setValue(
LayoutElementProto.SpanVerticalAlignment.forNumber(
alignment)));
return this;
}
@Override
@NonNull
public SpanImage build() {
return new SpanImage(mImpl.build(), mFingerprint);
}
}
}
/**
* Interface defining a single {@link Span}. Each {@link Span} forms part of a larger {@link
* Spannable} widget. At the moment, the only widgets which can be added to {@link Spannable}
* containers are {@link SpanText} and {@link SpanImage} elements.
*/
public interface Span {
/** Get the protocol buffer representation of this object. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
LayoutElementProto.Span toSpanProto();
/** Get the fingerprint for this object or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
Fingerprint getFingerprint();
/** Builder to create {@link Span} objects. */
@SuppressLint("StaticFinalBuilder")
interface Builder {
/** Builds an instance with values accumulated in this Builder. */
@NonNull
Span build();
}
}
@NonNull
static Span spanFromProto(@NonNull LayoutElementProto.Span proto) {
if (proto.hasText()) {
return SpanText.fromProto(proto.getText());
}
if (proto.hasImage()) {
return SpanImage.fromProto(proto.getImage());
}
throw new IllegalStateException("Proto was not a recognised instance of Span");
}
/**
* A container of {@link Span} elements. Currently, this supports {@link SpanImage} and {@link
* SpanText} elements, where each individual {@link Span} can have different styling applied to
* it but the resulting text will flow naturally. This allows sections of a paragraph of text to
* have different styling applied to it, for example, making one or two words bold or italic.
*/
public static final class Spannable implements LayoutElement {
private final LayoutElementProto.Spannable mImpl;
@Nullable private final Fingerprint mFingerprint;
Spannable(LayoutElementProto.Spannable impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets the {@link Span} elements that form this {@link Spannable}. Intended for testing
* purposes only.
*/
@NonNull
public List<Span> getSpans() {
List<Span> list = new ArrayList<>();
for (LayoutElementProto.Span item : mImpl.getSpansList()) {
list.add(LayoutElementBuilders.spanFromProto(item));
}
return Collections.unmodifiableList(list);
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public Modifiers getModifiers() {
if (mImpl.hasModifiers()) {
return Modifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
/**
* Gets the maximum number of lines that can be represented by the {@link Spannable}
* element. If not defined, the {@link Spannable} element will be treated as a single-line
* element. Intended for testing purposes only.
*/
@Nullable
public Int32Prop getMaxLines() {
if (mImpl.hasMaxLines()) {
return Int32Prop.fromProto(mImpl.getMaxLines());
} else {
return null;
}
}
/**
* Gets alignment of the {@link Spannable} content within its bounds. Note that a {@link
* Spannable} element will size itself to wrap its contents, so this option is meaningless
* for single-line content (for that, use alignment of the outer container). For multi-line
* content, however, this will set the alignment of lines relative to the {@link Spannable}
* element bounds. If not defined, defaults to TEXT_ALIGN_CENTER. Intended for testing
* purposes only.
*/
@Nullable
public HorizontalAlignmentProp getMultilineAlignment() {
if (mImpl.hasMultilineAlignment()) {
return HorizontalAlignmentProp.fromProto(mImpl.getMultilineAlignment());
} else {
return null;
}
}
/**
* Gets how to handle content which overflows the bound of the {@link Spannable} element. A
* {@link Spannable} element will grow as large as possible inside its parent container
* (while still respecting max_lines); if it cannot grow large enough to render all of its
* content, the content which cannot fit inside its container will be truncated. If not
* defined, defaults to TEXT_OVERFLOW_TRUNCATE. Intended for testing purposes only.
*/
@Nullable
public TextOverflowProp getOverflow() {
if (mImpl.hasOverflow()) {
return TextOverflowProp.fromProto(mImpl.getOverflow());
} else {
return null;
}
}
/**
* Gets the explicit height between lines of text. This is equivalent to the vertical
* distance between subsequent baselines. If not specified, defaults the font's recommended
* interline spacing. Intended for testing purposes only.
*/
@Nullable
public SpProp getLineHeight() {
if (mImpl.hasLineHeight()) {
return SpProp.fromProto(mImpl.getLineHeight());
} else {
return null;
}
}
/**
* Gets the number of times to repeat the Marquee animation. Only applies when overflow is
* TEXT_OVERFLOW_MARQUEE. Set to -1 to repeat indefinitely. Defaults to repeat indefinitely.
*
* @since 1.2
*/
@ProtoLayoutExperimental
@IntRange(from = -1)
public int getMarqueeIterations() {
return mImpl.getMarqueeParameters().getIterations();
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static Spannable fromProto(@NonNull LayoutElementProto.Spannable proto) {
return new Spannable(proto, null);
}
@NonNull
LayoutElementProto.Spannable toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.LayoutElement toLayoutElementProto() {
return LayoutElementProto.LayoutElement.newBuilder().setSpannable(mImpl).build();
}
/** Builder for {@link Spannable}. */
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Spannable.Builder mImpl =
LayoutElementProto.Spannable.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-1690284372);
public Builder() {}
/** Adds one item to the {@link Span} elements that form this {@link Spannable}. */
@NonNull
public Builder addSpan(@NonNull Span span) {
mImpl.addSpans(span.toSpanProto());
mFingerprint.recordPropertyUpdate(
1, checkNotNull(span.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull Modifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the maximum number of lines that can be represented by the {@link Spannable}
* element. If not defined, the {@link Spannable} element will be treated as a
* single-line element.
*/
@NonNull
public Builder setMaxLines(@NonNull Int32Prop maxLines) {
mImpl.setMaxLines(maxLines.toProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(maxLines.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the maximum number of lines that can be represented by the {@link Spannable}
* element. If not defined, the {@link Spannable} element will be treated as a
* single-line element.
*/
@NonNull
public Builder setMaxLines(@IntRange(from = 1) int maxLines) {
mImpl.setMaxLines(TypesProto.Int32Prop.newBuilder().setValue(maxLines));
return this;
}
/**
* Sets alignment of the {@link Spannable} content within its bounds. Note that a {@link
* Spannable} element will size itself to wrap its contents, so this option is
* meaningless for single-line content (for that, use alignment of the outer container).
* For multi-line content, however, this will set the alignment of lines relative to the
* {@link Spannable} element bounds. If not defined, defaults to TEXT_ALIGN_CENTER.
*/
@NonNull
public Builder setMultilineAlignment(
@NonNull HorizontalAlignmentProp multilineAlignment) {
mImpl.setMultilineAlignment(multilineAlignment.toProto());
mFingerprint.recordPropertyUpdate(
4, checkNotNull(multilineAlignment.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets alignment of the {@link Spannable} content within its bounds. Note that a {@link
* Spannable} element will size itself to wrap its contents, so this option is
* meaningless for single-line content (for that, use alignment of the outer container).
* For multi-line content, however, this will set the alignment of lines relative to the
* {@link Spannable} element bounds. If not defined, defaults to TEXT_ALIGN_CENTER.
*/
@NonNull
public Builder setMultilineAlignment(@HorizontalAlignment int multilineAlignment) {
mImpl.setMultilineAlignment(
AlignmentProto.HorizontalAlignmentProp.newBuilder()
.setValue(
AlignmentProto.HorizontalAlignment.forNumber(
multilineAlignment)));
return this;
}
/**
* Sets how to handle content which overflows the bound of the {@link Spannable}
* element. A {@link Spannable} element will grow as large as possible inside its parent
* container (while still respecting max_lines); if it cannot grow large enough to
* render all of its content, the content which cannot fit inside its container will be
* truncated. If not defined, defaults to TEXT_OVERFLOW_TRUNCATE.
*/
@NonNull
public Builder setOverflow(@NonNull TextOverflowProp overflow) {
mImpl.setOverflow(overflow.toProto());
mFingerprint.recordPropertyUpdate(
5, checkNotNull(overflow.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets how to handle content which overflows the bound of the {@link Spannable}
* element. A {@link Spannable} element will grow as large as possible inside its parent
* container (while still respecting max_lines); if it cannot grow large enough to
* render all of its content, the content which cannot fit inside its container will be
* truncated. If not defined, defaults to TEXT_OVERFLOW_TRUNCATE.
*/
@NonNull
public Builder setOverflow(@TextOverflow int overflow) {
mImpl.setOverflow(
LayoutElementProto.TextOverflowProp.newBuilder()
.setValue(LayoutElementProto.TextOverflow.forNumber(overflow)));
return this;
}
/**
* Sets the explicit height between lines of text. This is equivalent to the vertical
* distance between subsequent baselines. If not specified, defaults the font's
* recommended interline spacing.
*/
@NonNull
public Builder setLineHeight(@NonNull SpProp lineHeight) {
mImpl.setLineHeight(lineHeight.toProto());
mFingerprint.recordPropertyUpdate(
7, checkNotNull(lineHeight.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the number of times to repeat the Marquee animation. Only applies when overflow
* is TEXT_OVERFLOW_MARQUEE. Set to -1 to repeat indefinitely. Defaults to repeat
* indefinitely.
*
* @since 1.2
*/
@ProtoLayoutExperimental
@NonNull
public Builder setMarqueeIterations(@IntRange(from = -1) int marqueeIterations) {
mImpl.setMarqueeParameters(
LayoutElementProto.MarqueeParameters.newBuilder()
.setIterations(marqueeIterations));
mFingerprint.recordPropertyUpdate(8, marqueeIterations);
return this;
}
@Override
@NonNull
public Spannable build() {
return new Spannable(mImpl.build(), mFingerprint);
}
}
}
/**
* A column of elements. Each child element will be laid out vertically, one after another (i.e.
* stacking down). This element will size itself to the smallest size required to hold all of
* its children (e.g. if it contains three elements sized 10x10, 20x20 and 30x30, the resulting
* column will be 30x60).
*
* <p>If specified, horizontal_alignment can be used to control the gravity inside the
* container, affecting the horizontal placement of children whose width are smaller than the
* resulting column width.
*/
public static final class Column implements LayoutElement {
private final LayoutElementProto.Column mImpl;
@Nullable private final Fingerprint mFingerprint;
Column(LayoutElementProto.Column impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets the list of child elements to place inside this {@link Column}. Intended for testing
* purposes only.
*/
@NonNull
public List<LayoutElement> getContents() {
List<LayoutElement> list = new ArrayList<>();
for (LayoutElementProto.LayoutElement item : mImpl.getContentsList()) {
list.add(LayoutElementBuilders.layoutElementFromProto(item));
}
return Collections.unmodifiableList(list);
}
/**
* Gets the horizontal alignment of elements inside this column, if they are narrower than
* the resulting width of the column. If not defined, defaults to HORIZONTAL_ALIGN_CENTER.
* Intended for testing purposes only.
*/
@Nullable
public HorizontalAlignmentProp getHorizontalAlignment() {
if (mImpl.hasHorizontalAlignment()) {
return HorizontalAlignmentProp.fromProto(mImpl.getHorizontalAlignment());
} else {
return null;
}
}
/**
* Gets the width of this column. If not defined, this will size itself to fit all of its
* children (i.e. a WrappedDimension). Intended for testing purposes only.
*/
@Nullable
public ContainerDimension getWidth() {
if (mImpl.hasWidth()) {
return DimensionBuilders.containerDimensionFromProto(mImpl.getWidth());
} else {
return null;
}
}
/**
* Gets the height of this column. If not defined, this will size itself to fit all of its
* children (i.e. a WrappedDimension). Intended for testing purposes only.
*/
@Nullable
public ContainerDimension getHeight() {
if (mImpl.hasHeight()) {
return DimensionBuilders.containerDimensionFromProto(mImpl.getHeight());
} else {
return null;
}
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public Modifiers getModifiers() {
if (mImpl.hasModifiers()) {
return Modifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static Column fromProto(@NonNull LayoutElementProto.Column proto) {
return new Column(proto, null);
}
@NonNull
LayoutElementProto.Column toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.LayoutElement toLayoutElementProto() {
return LayoutElementProto.LayoutElement.newBuilder().setColumn(mImpl).build();
}
/** Builder for {@link Column}. */
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Column.Builder mImpl =
LayoutElementProto.Column.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-1411218529);
public Builder() {}
/** Adds one item to the list of child elements to place inside this {@link Column}. */
@NonNull
public Builder addContent(@NonNull LayoutElement content) {
mImpl.addContents(content.toLayoutElementProto());
mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
return this;
}
/**
* Sets the horizontal alignment of elements inside this column, if they are narrower
* than the resulting width of the column. If not defined, defaults to
* HORIZONTAL_ALIGN_CENTER.
*/
@NonNull
public Builder setHorizontalAlignment(
@NonNull HorizontalAlignmentProp horizontalAlignment) {
mImpl.setHorizontalAlignment(horizontalAlignment.toProto());
mFingerprint.recordPropertyUpdate(
2,
checkNotNull(horizontalAlignment.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the horizontal alignment of elements inside this column, if they are narrower
* than the resulting width of the column. If not defined, defaults to
* HORIZONTAL_ALIGN_CENTER.
*/
@NonNull
public Builder setHorizontalAlignment(@HorizontalAlignment int horizontalAlignment) {
mImpl.setHorizontalAlignment(
AlignmentProto.HorizontalAlignmentProp.newBuilder()
.setValue(
AlignmentProto.HorizontalAlignment.forNumber(
horizontalAlignment)));
return this;
}
/**
* Sets the width of this column. If not defined, this will size itself to fit all of
* its children (i.e. a WrappedDimension).
*/
@NonNull
public Builder setWidth(@NonNull ContainerDimension width) {
mImpl.setWidth(width.toContainerDimensionProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the height of this column. If not defined, this will size itself to fit all of
* its children (i.e. a WrappedDimension).
*/
@NonNull
public Builder setHeight(@NonNull ContainerDimension height) {
mImpl.setHeight(height.toContainerDimensionProto());
mFingerprint.recordPropertyUpdate(
4, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull Modifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
5, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
@Override
@NonNull
public Column build() {
return new Column(mImpl.build(), mFingerprint);
}
}
}
/**
* A row of elements. Each child will be laid out horizontally, one after another (i.e. stacking
* to the right). This element will size itself to the smallest size required to hold all of its
* children (e.g. if it contains three elements sized 10x10, 20x20 and 30x30, the resulting row
* will be 60x30).
*
* <p>If specified, vertical_alignment can be used to control the gravity inside the container,
* affecting the vertical placement of children whose width are smaller than the resulting row
* height.
*/
public static final class Row implements LayoutElement {
private final LayoutElementProto.Row mImpl;
@Nullable private final Fingerprint mFingerprint;
Row(LayoutElementProto.Row impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets the list of child elements to place inside this {@link Row}. Intended for testing
* purposes only.
*/
@NonNull
public List<LayoutElement> getContents() {
List<LayoutElement> list = new ArrayList<>();
for (LayoutElementProto.LayoutElement item : mImpl.getContentsList()) {
list.add(LayoutElementBuilders.layoutElementFromProto(item));
}
return Collections.unmodifiableList(list);
}
/**
* Gets the vertical alignment of elements inside this row, if they are narrower than the
* resulting height of the row. If not defined, defaults to VERTICAL_ALIGN_CENTER. Intended
* for testing purposes only.
*/
@Nullable
public VerticalAlignmentProp getVerticalAlignment() {
if (mImpl.hasVerticalAlignment()) {
return VerticalAlignmentProp.fromProto(mImpl.getVerticalAlignment());
} else {
return null;
}
}
/**
* Gets the width of this row. If not defined, this will size itself to fit all of its
* children (i.e. a WrappedDimension). Intended for testing purposes only.
*/
@Nullable
public ContainerDimension getWidth() {
if (mImpl.hasWidth()) {
return DimensionBuilders.containerDimensionFromProto(mImpl.getWidth());
} else {
return null;
}
}
/**
* Gets the height of this row. If not defined, this will size itself to fit all of its
* children (i.e. a WrappedDimension). Intended for testing purposes only.
*/
@Nullable
public ContainerDimension getHeight() {
if (mImpl.hasHeight()) {
return DimensionBuilders.containerDimensionFromProto(mImpl.getHeight());
} else {
return null;
}
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public Modifiers getModifiers() {
if (mImpl.hasModifiers()) {
return Modifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static Row fromProto(@NonNull LayoutElementProto.Row proto) {
return new Row(proto, null);
}
@NonNull
LayoutElementProto.Row toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.LayoutElement toLayoutElementProto() {
return LayoutElementProto.LayoutElement.newBuilder().setRow(mImpl).build();
}
/** Builder for {@link Row}. */
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Row.Builder mImpl =
LayoutElementProto.Row.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(1537205448);
public Builder() {}
/** Adds one item to the list of child elements to place inside this {@link Row}. */
@NonNull
public Builder addContent(@NonNull LayoutElement content) {
mImpl.addContents(content.toLayoutElementProto());
mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
return this;
}
/**
* Sets the vertical alignment of elements inside this row, if they are narrower than
* the resulting height of the row. If not defined, defaults to VERTICAL_ALIGN_CENTER.
*/
@NonNull
public Builder setVerticalAlignment(@NonNull VerticalAlignmentProp verticalAlignment) {
mImpl.setVerticalAlignment(verticalAlignment.toProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(verticalAlignment.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the vertical alignment of elements inside this row, if they are narrower than
* the resulting height of the row. If not defined, defaults to VERTICAL_ALIGN_CENTER.
*/
@NonNull
public Builder setVerticalAlignment(@VerticalAlignment int verticalAlignment) {
mImpl.setVerticalAlignment(
AlignmentProto.VerticalAlignmentProp.newBuilder()
.setValue(
AlignmentProto.VerticalAlignment.forNumber(
verticalAlignment)));
return this;
}
/**
* Sets the width of this row. If not defined, this will size itself to fit all of its
* children (i.e. a WrappedDimension).
*/
@NonNull
public Builder setWidth(@NonNull ContainerDimension width) {
mImpl.setWidth(width.toContainerDimensionProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the height of this row. If not defined, this will size itself to fit all of its
* children (i.e. a WrappedDimension).
*/
@NonNull
public Builder setHeight(@NonNull ContainerDimension height) {
mImpl.setHeight(height.toContainerDimensionProto());
mFingerprint.recordPropertyUpdate(
4, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull Modifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
5, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
@Override
@NonNull
public Row build() {
return new Row(mImpl.build(), mFingerprint);
}
}
}
/**
* An arc container. This container will fill itself to a circle, which fits inside its parent
* container, and all of its children will be placed on that circle. The fields anchor_angle and
* anchor_type can be used to specify where to draw children within this circle.
*/
public static final class Arc implements LayoutElement {
private final LayoutElementProto.Arc mImpl;
@Nullable private final Fingerprint mFingerprint;
Arc(LayoutElementProto.Arc impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets contents of this container. Intended for testing purposes only. */
@NonNull
public List<ArcLayoutElement> getContents() {
List<ArcLayoutElement> list = new ArrayList<>();
for (LayoutElementProto.ArcLayoutElement item : mImpl.getContentsList()) {
list.add(LayoutElementBuilders.arcLayoutElementFromProto(item));
}
return Collections.unmodifiableList(list);
}
/**
* Gets the angle for the anchor, used with anchor_type to determine where to draw children.
* Note that 0 degrees is the 12 o clock position on a device, and the angle sweeps
* clockwise. If not defined, defaults to 0 degrees.
*
* <p>Values do not have to be clamped to the range 0-360; values less than 0 degrees will
* sweep anti-clockwise (i.e. -90 degrees is equivalent to 270 degrees), and values >360
* will be be placed at X mod 360 degrees. Intended for testing purposes only.
*/
@Nullable
public DegreesProp getAnchorAngle() {
if (mImpl.hasAnchorAngle()) {
return DegreesProp.fromProto(mImpl.getAnchorAngle());
} else {
return null;
}
}
/**
* Gets the bounding constraints for the layout affected by the dynamic value from {@link
* #getAnchorAngle()}.
*
* @since 1.2
*/
@Nullable
public AngularLayoutConstraint getLayoutConstraintsForDynamicAnchorAngle() {
if (mImpl.hasAnchorAngle()) {
return AngularLayoutConstraint.fromProto(mImpl.getAnchorAngle());
} else {
return null;
}
}
/**
* Gets how to align the contents of this container relative to anchor_angle. If not
* defined, defaults to ARC_ANCHOR_CENTER. Intended for testing purposes only.
*/
@Nullable
public ArcAnchorTypeProp getAnchorType() {
if (mImpl.hasAnchorType()) {
return ArcAnchorTypeProp.fromProto(mImpl.getAnchorType());
} else {
return null;
}
}
/**
* Gets vertical alignment of elements within the arc. If the {@link Arc}'s thickness is
* larger than the thickness of the element being drawn, this controls whether the element
* should be drawn towards the inner or outer edge of the arc, or drawn in the center. If
* not defined, defaults to VERTICAL_ALIGN_CENTER. Intended for testing purposes only.
*/
@Nullable
public VerticalAlignmentProp getVerticalAlign() {
if (mImpl.hasVerticalAlign()) {
return VerticalAlignmentProp.fromProto(mImpl.getVerticalAlign());
} else {
return null;
}
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public Modifiers getModifiers() {
if (mImpl.hasModifiers()) {
return Modifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static Arc fromProto(@NonNull LayoutElementProto.Arc proto) {
return new Arc(proto, null);
}
@NonNull
LayoutElementProto.Arc toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.LayoutElement toLayoutElementProto() {
return LayoutElementProto.LayoutElement.newBuilder().setArc(mImpl).build();
}
/** Builder for {@link Arc}. */
public static final class Builder implements LayoutElement.Builder {
private final LayoutElementProto.Arc.Builder mImpl =
LayoutElementProto.Arc.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(299028337);
public Builder() {}
/** Adds one item to contents of this container. */
@NonNull
public Builder addContent(@NonNull ArcLayoutElement content) {
mImpl.addContents(content.toArcLayoutElementProto());
mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
return this;
}
/**
* Sets the angle for the anchor, used with anchor_type to determine where to draw
* children. Note that 0 degrees is the 12 o clock position on a device, and the angle
* sweeps clockwise. If not defined, defaults to 0 degrees.
*
* <p>Values do not have to be clamped to the range 0-360; values less than 0 degrees
* will sweep anti-clockwise (i.e. -90 degrees is equivalent to 270 degrees), and values
* >360 will be be placed at X mod 360 degrees.
*
* <p>This field is made bindable from version 1.2 and will use the dynamic value (if
* set). Older renderers will still consider this field as non-bindable and will use the
* static value.
*
* <p>When using a dynamic value, make sure to specify the bounding constraints for the
* affected layout element through {@link
* #setLayoutConstraintsForDynamicAnchorAngle(AngularLayoutConstraint)} otherwise {@link
* #build()} fails.
*
* @since 1.0
*/
@NonNull
public Builder setAnchorAngle(@NonNull DegreesProp anchorAngle) {
mImpl.setAnchorAngle(anchorAngle.toProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(anchorAngle.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the bounding constraints for the layout affected by the dynamic value from
* {@link #setAnchorAngle(DegreesProp)}}.
*
* @since 1.2
*/
@NonNull
public Builder setLayoutConstraintsForDynamicAnchorAngle(
@NonNull DimensionBuilders.AngularLayoutConstraint angularLayoutConstraint) {
mImpl.mergeAnchorAngle(angularLayoutConstraint.toProto());
mFingerprint.recordPropertyUpdate(
2,
checkNotNull(angularLayoutConstraint.getFingerprint())
.aggregateValueAsInt());
return this;
}
/**
* Sets how to align the contents of this container relative to anchor_angle. If not
* defined, defaults to ARC_ANCHOR_CENTER.
*/
@NonNull
public Builder setAnchorType(@NonNull ArcAnchorTypeProp anchorType) {
mImpl.setAnchorType(anchorType.toProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(anchorType.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets how to align the contents of this container relative to anchor_angle. If not
* defined, defaults to ARC_ANCHOR_CENTER.
*/
@NonNull
public Builder setAnchorType(@ArcAnchorType int anchorType) {
mImpl.setAnchorType(
AlignmentProto.ArcAnchorTypeProp.newBuilder()
.setValue(AlignmentProto.ArcAnchorType.forNumber(anchorType)));
return this;
}
/**
* Sets vertical alignment of elements within the arc. If the {@link Arc}'s thickness is
* larger than the thickness of the element being drawn, this controls whether the
* element should be drawn towards the inner or outer edge of the arc, or drawn in the
* center. If not defined, defaults to VERTICAL_ALIGN_CENTER.
*/
@NonNull
public Builder setVerticalAlign(@NonNull VerticalAlignmentProp verticalAlign) {
mImpl.setVerticalAlign(verticalAlign.toProto());
mFingerprint.recordPropertyUpdate(
4, checkNotNull(verticalAlign.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets vertical alignment of elements within the arc. If the {@link Arc}'s thickness is
* larger than the thickness of the element being drawn, this controls whether the
* element should be drawn towards the inner or outer edge of the arc, or drawn in the
* center. If not defined, defaults to VERTICAL_ALIGN_CENTER.
*/
@NonNull
public Builder setVerticalAlign(@VerticalAlignment int verticalAlign) {
mImpl.setVerticalAlign(
AlignmentProto.VerticalAlignmentProp.newBuilder()
.setValue(
AlignmentProto.VerticalAlignment.forNumber(verticalAlign)));
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull Modifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
5, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
@Override
@NonNull
public Arc build() {
DimensionProto.DegreesProp anchorAngle = mImpl.getAnchorAngle();
if (anchorAngle.hasDynamicValue() && !anchorAngle.hasValueForLayout()) {
throw new IllegalStateException(
"anchorAngle with dynamic value requires "
+ "layoutConstraintsForDynamicAnchorAngle to be present.");
}
return new Arc(mImpl.build(), mFingerprint);
}
}
}
/** A text element that can be used in an {@link Arc}. */
public static final class ArcText implements ArcLayoutElement {
private final LayoutElementProto.ArcText mImpl;
@Nullable private final Fingerprint mFingerprint;
ArcText(LayoutElementProto.ArcText impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the text to render. Intended for testing purposes only. */
@Nullable
public StringProp getText() {
if (mImpl.hasText()) {
return StringProp.fromProto(mImpl.getText());
} else {
return null;
}
}
/**
* Gets the style of font to use (size, bold etc). If not specified, defaults to the
* platform's default body font. Intended for testing purposes only.
*/
@Nullable
public FontStyle getFontStyle() {
if (mImpl.hasFontStyle()) {
return FontStyle.fromProto(mImpl.getFontStyle());
} else {
return null;
}
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public ArcModifiers getModifiers() {
if (mImpl.hasModifiers()) {
return ArcModifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static ArcText fromProto(@NonNull LayoutElementProto.ArcText proto) {
return new ArcText(proto, null);
}
@NonNull
LayoutElementProto.ArcText toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.ArcLayoutElement toArcLayoutElementProto() {
return LayoutElementProto.ArcLayoutElement.newBuilder().setText(mImpl).build();
}
/** Builder for {@link ArcText}. */
public static final class Builder implements ArcLayoutElement.Builder {
private final LayoutElementProto.ArcText.Builder mImpl =
LayoutElementProto.ArcText.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(434391973);
public Builder() {}
/**
* Sets the text to render.
*
* <p>Note that this field only supports static values.
*
* @since 1.0
*/
@NonNull
public Builder setText(@NonNull StringProp text) {
if (text.getDynamicValue() != null) {
throw new IllegalArgumentException(
"ArcText.Builder.setText doesn't support dynamic values.");
}
mImpl.setText(text.toProto());
mFingerprint.recordPropertyUpdate(
1, checkNotNull(text.getFingerprint()).aggregateValueAsInt());
return this;
}
/** Sets the text to render. */
@NonNull
public Builder setText(@NonNull String text) {
mImpl.setText(TypesProto.StringProp.newBuilder().setValue(text));
return this;
}
/**
* Sets the style of font to use (size, bold etc). If not specified, defaults to the
* platform's default body font.
*/
@NonNull
public Builder setFontStyle(@NonNull FontStyle fontStyle) {
mImpl.setFontStyle(fontStyle.toProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(fontStyle.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull ArcModifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
@Override
@NonNull
public ArcText build() {
return new ArcText(mImpl.build(), mFingerprint);
}
}
}
/** A line that can be used in an {@link Arc} and renders as a round progress bar. */
public static final class ArcLine implements ArcLayoutElement {
private final LayoutElementProto.ArcLine mImpl;
@Nullable private final Fingerprint mFingerprint;
ArcLine(LayoutElementProto.ArcLine impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets the length of this line, in degrees. If not defined, defaults to 0. Intended for
* testing purposes only.
*/
@Nullable
public DegreesProp getLength() {
if (mImpl.hasLength()) {
return DegreesProp.fromProto(mImpl.getLength());
} else {
return null;
}
}
/**
* Gets the bounding constraints for the layout affected by the dynamic value from {@link
* #getLength()}.
*
* @since 1.2
*/
@Nullable
public AngularLayoutConstraint getLayoutConstraintsForDynamicLength() {
if (mImpl.hasLength()) {
return AngularLayoutConstraint.fromProto(mImpl.getLength());
} else {
return null;
}
}
/**
* Gets the thickness of this line. If not defined, defaults to 0. Intended for testing
* purposes only.
*/
@Nullable
public DpProp getThickness() {
if (mImpl.hasThickness()) {
return DpProp.fromProto(mImpl.getThickness());
} else {
return null;
}
}
/** Gets the color of this line. Intended for testing purposes only. */
@Nullable
public ColorProp getColor() {
if (mImpl.hasColor()) {
return ColorProp.fromProto(mImpl.getColor());
} else {
return null;
}
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public ArcModifiers getModifiers() {
if (mImpl.hasModifiers()) {
return ArcModifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static ArcLine fromProto(@NonNull LayoutElementProto.ArcLine proto) {
return new ArcLine(proto, null);
}
@NonNull
LayoutElementProto.ArcLine toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.ArcLayoutElement toArcLayoutElementProto() {
return LayoutElementProto.ArcLayoutElement.newBuilder().setLine(mImpl).build();
}
/** Builder for {@link ArcLine}. */
public static final class Builder implements ArcLayoutElement.Builder {
private final LayoutElementProto.ArcLine.Builder mImpl =
LayoutElementProto.ArcLine.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-1371793535);
public Builder() {}
/**
* Sets the length of this line, in degrees. If not defined, defaults to 0.
*
* <p>This field is made bindable from version 1.2 and will use the dynamic value (if
* set). Older renderers will still consider this field as non-bindable and will use the
* static value.
*
* <p>When using a dynamic value, make sure to specify the bounding constraints for the
* affected layout element through {@link
* #setLayoutConstraintsForDynamicLength(AngularLayoutConstraint)} otherwise {@link
* #build()} fails.
*
* @since 1.0
*/
@NonNull
public Builder setLength(@NonNull DegreesProp length) {
mImpl.mergeLength(length.toProto());
mFingerprint.recordPropertyUpdate(
1, checkNotNull(length.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the bounding constraints for the layout affected by the dynamic value from
* {@link #setLength(DegreesProp)}.
*
* @since 1.2
*/
@NonNull
public Builder setLayoutConstraintsForDynamicLength(
@NonNull DimensionBuilders.AngularLayoutConstraint angularLayoutConstraint) {
mImpl.mergeLength(angularLayoutConstraint.toProto());
mFingerprint.recordPropertyUpdate(
1,
checkNotNull(angularLayoutConstraint.getFingerprint())
.aggregateValueAsInt());
return this;
}
/** Sets the thickness of this line. If not defined, defaults to 0. */
@NonNull
public Builder setThickness(@NonNull DpProp thickness) {
mImpl.setThickness(thickness.toProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(thickness.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets the color of this line.
*
* <p>This field is made bindable from version 1.2 and will use the dynamic value (if
* set). Older renderers will still consider this field as non-bindable and will use the
* static value.
*
* @since 1.0
*/
@NonNull
public Builder setColor(@NonNull ColorProp color) {
mImpl.setColor(color.toProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(color.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull ArcModifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
4, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
@Override
@NonNull
public ArcLine build() {
DimensionProto.DegreesProp length = mImpl.getLength();
if (length.hasDynamicValue() && !length.hasValueForLayout()) {
throw new IllegalStateException(
"length with dynamic value requires "
+ "layoutConstraintsForDynamicLength to be present.");
}
return new ArcLine(mImpl.build(), mFingerprint);
}
}
}
/** A simple spacer used to provide padding between adjacent elements in an {@link Arc}. */
public static final class ArcSpacer implements ArcLayoutElement {
private final LayoutElementProto.ArcSpacer mImpl;
@Nullable private final Fingerprint mFingerprint;
ArcSpacer(LayoutElementProto.ArcSpacer impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/**
* Gets the length of this spacer, in degrees. If not defined, defaults to 0. Intended for
* testing purposes only.
*/
@Nullable
public DegreesProp getLength() {
if (mImpl.hasLength()) {
return DegreesProp.fromProto(mImpl.getLength());
} else {
return null;
}
}
/**
* Gets the thickness of this spacer, in DP. If not defined, defaults to 0. Intended for
* testing purposes only.
*/
@Nullable
public DpProp getThickness() {
if (mImpl.hasThickness()) {
return DpProp.fromProto(mImpl.getThickness());
} else {
return null;
}
}
/**
* Gets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
* Intended for testing purposes only.
*/
@Nullable
public ArcModifiers getModifiers() {
if (mImpl.hasModifiers()) {
return ArcModifiers.fromProto(mImpl.getModifiers());
} else {
return null;
}
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static ArcSpacer fromProto(@NonNull LayoutElementProto.ArcSpacer proto) {
return new ArcSpacer(proto, null);
}
@NonNull
LayoutElementProto.ArcSpacer toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.ArcLayoutElement toArcLayoutElementProto() {
return LayoutElementProto.ArcLayoutElement.newBuilder().setSpacer(mImpl).build();
}
/** Builder for {@link ArcSpacer}. */
public static final class Builder implements ArcLayoutElement.Builder {
private final LayoutElementProto.ArcSpacer.Builder mImpl =
LayoutElementProto.ArcSpacer.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-179760535);
public Builder() {}
/** Sets the length of this spacer, in degrees. If not defined, defaults to 0. */
@NonNull
public Builder setLength(@NonNull DegreesProp length) {
mImpl.setLength(length.toProto());
mFingerprint.recordPropertyUpdate(
1, checkNotNull(length.getFingerprint()).aggregateValueAsInt());
return this;
}
/** Sets the thickness of this spacer, in DP. If not defined, defaults to 0. */
@NonNull
public Builder setThickness(@NonNull DpProp thickness) {
mImpl.setThickness(thickness.toProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(thickness.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets {@link androidx.wear.protolayout.ModifiersBuilders.Modifiers} for this element.
*/
@NonNull
public Builder setModifiers(@NonNull ArcModifiers modifiers) {
mImpl.setModifiers(modifiers.toProto());
mFingerprint.recordPropertyUpdate(
3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
return this;
}
@Override
@NonNull
public ArcSpacer build() {
return new ArcSpacer(mImpl.build(), mFingerprint);
}
}
}
/** A container that allows a standard {@link LayoutElement} to be added to an {@link Arc}. */
public static final class ArcAdapter implements ArcLayoutElement {
private final LayoutElementProto.ArcAdapter mImpl;
@Nullable private final Fingerprint mFingerprint;
ArcAdapter(LayoutElementProto.ArcAdapter impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the element to adapt to an {@link Arc}. Intended for testing purposes only. */
@Nullable
public LayoutElement getContent() {
if (mImpl.hasContent()) {
return LayoutElementBuilders.layoutElementFromProto(mImpl.getContent());
} else {
return null;
}
}
/**
* Gets whether this adapter's contents should be rotated, according to its position in the
* arc or not. As an example, assume that an {@link Image} has been added to the arc, and
* ends up at the 3 o clock position. If rotate_contents = true, the image will be placed at
* the 3 o clock position, and will be rotated clockwise through 90 degrees. If
* rotate_contents = false, the image will be placed at the 3 o clock position, but itself
* will not be rotated. If not defined, defaults to false. Intended for testing purposes
* only.
*/
@Nullable
public BoolProp getRotateContents() {
if (mImpl.hasRotateContents()) {
return BoolProp.fromProto(mImpl.getRotateContents());
} else {
return null;
}
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static ArcAdapter fromProto(@NonNull LayoutElementProto.ArcAdapter proto) {
return new ArcAdapter(proto, null);
}
@NonNull
LayoutElementProto.ArcAdapter toProto() {
return mImpl;
}
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.ArcLayoutElement toArcLayoutElementProto() {
return LayoutElementProto.ArcLayoutElement.newBuilder().setAdapter(mImpl).build();
}
/** Builder for {@link ArcAdapter}. */
public static final class Builder implements ArcLayoutElement.Builder {
private final LayoutElementProto.ArcAdapter.Builder mImpl =
LayoutElementProto.ArcAdapter.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(1696473935);
public Builder() {}
/** Sets the element to adapt to an {@link Arc}. */
@NonNull
public Builder setContent(@NonNull LayoutElement content) {
mImpl.setContent(content.toLayoutElementProto());
mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
return this;
}
/**
* Sets whether this adapter's contents should be rotated, according to its position in
* the arc or not. As an example, assume that an {@link Image} has been added to the
* arc, and ends up at the 3 o clock position. If rotate_contents = true, the image will
* be placed at the 3 o clock position, and will be rotated clockwise through 90
* degrees. If rotate_contents = false, the image will be placed at the 3 o clock
* position, but itself will not be rotated. If not defined, defaults to false.
*/
@NonNull
public Builder setRotateContents(@NonNull BoolProp rotateContents) {
mImpl.setRotateContents(rotateContents.toProto());
mFingerprint.recordPropertyUpdate(
2, checkNotNull(rotateContents.getFingerprint()).aggregateValueAsInt());
return this;
}
/**
* Sets whether this adapter's contents should be rotated, according to its position in
* the arc or not. As an example, assume that an {@link Image} has been added to the
* arc, and ends up at the 3 o clock position. If rotate_contents = true, the image will
* be placed at the 3 o clock position, and will be rotated clockwise through 90
* degrees. If rotate_contents = false, the image will be placed at the 3 o clock
* position, but itself will not be rotated. If not defined, defaults to false.
*/
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setRotateContents(boolean rotateContents) {
mImpl.setRotateContents(TypesProto.BoolProp.newBuilder().setValue(rotateContents));
return this;
}
@Override
@NonNull
public ArcAdapter build() {
return new ArcAdapter(mImpl.build(), mFingerprint);
}
}
}
/**
* Interface defining the root of all layout elements. This exists to act as a holder for all of
* the actual layout elements above.
*/
public interface LayoutElement {
/** Get the protocol buffer representation of this object. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
LayoutElementProto.LayoutElement toLayoutElementProto();
/** Get the fingerprint for this object or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
Fingerprint getFingerprint();
/** Builder to create {@link LayoutElement} objects. */
@SuppressLint("StaticFinalBuilder")
interface Builder {
/** Builds an instance with values accumulated in this Builder. */
@NonNull
LayoutElement build();
}
}
/** Creates a new wrapper instance from the proto. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public static LayoutElement layoutElementFromProto(
@NonNull LayoutElementProto.LayoutElement proto) {
if (proto.hasColumn()) {
return Column.fromProto(proto.getColumn());
}
if (proto.hasRow()) {
return Row.fromProto(proto.getRow());
}
if (proto.hasBox()) {
return Box.fromProto(proto.getBox());
}
if (proto.hasSpacer()) {
return Spacer.fromProto(proto.getSpacer());
}
if (proto.hasText()) {
return Text.fromProto(proto.getText());
}
if (proto.hasImage()) {
return Image.fromProto(proto.getImage());
}
if (proto.hasArc()) {
return Arc.fromProto(proto.getArc());
}
if (proto.hasSpannable()) {
return Spannable.fromProto(proto.getSpannable());
}
throw new IllegalStateException("Proto was not a recognised instance of LayoutElement");
}
/**
* Interface defining the root of all elements that can be used in an {@link Arc}. This exists
* to act as a holder for all of the actual arc layout elements above.
*/
public interface ArcLayoutElement {
/** Get the protocol buffer representation of this object. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
LayoutElementProto.ArcLayoutElement toArcLayoutElementProto();
/** Get the fingerprint for this object or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
Fingerprint getFingerprint();
/** Builder to create {@link ArcLayoutElement} objects. */
@SuppressLint("StaticFinalBuilder")
interface Builder {
/** Builds an instance with values accumulated in this Builder. */
@NonNull
ArcLayoutElement build();
}
}
@NonNull
static ArcLayoutElement arcLayoutElementFromProto(
@NonNull LayoutElementProto.ArcLayoutElement proto) {
if (proto.hasText()) {
return ArcText.fromProto(proto.getText());
}
if (proto.hasLine()) {
return ArcLine.fromProto(proto.getLine());
}
if (proto.hasSpacer()) {
return ArcSpacer.fromProto(proto.getSpacer());
}
if (proto.hasAdapter()) {
return ArcAdapter.fromProto(proto.getAdapter());
}
throw new IllegalStateException("Proto was not a recognised instance of ArcLayoutElement");
}
/** A complete layout. */
public static final class Layout {
private final LayoutElementProto.Layout mImpl;
private Layout(LayoutElementProto.Layout impl) {
this.mImpl = impl;
}
/** Gets the root element in the layout. Intended for testing purposes only. */
@Nullable
public LayoutElement getRoot() {
if (mImpl.hasRoot()) {
return LayoutElementBuilders.layoutElementFromProto(mImpl.getRoot());
} else {
return null;
}
}
/** Creates a {@link Layout} object containing the given layout element. */
@NonNull
public static Layout fromLayoutElement(@NonNull LayoutElement layoutElement) {
return new Builder().setRoot(layoutElement).build();
}
/** Converts to byte array representation. */
@NonNull
@ProtoLayoutExperimental
public byte[] toByteArray() {
return mImpl.toByteArray();
}
/** Converts from byte array representation. */
@SuppressWarnings("ProtoParseWithRegistry")
@Nullable
@ProtoLayoutExperimental
public static Layout fromByteArray(@NonNull byte[] byteArray) {
try {
return fromProto(LayoutElementProto.Layout.parseFrom(byteArray));
} catch (InvalidProtocolBufferException e) {
return null;
}
}
/** Creates a new wrapper instance from the proto. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public static Layout fromProto(@NonNull LayoutElementProto.Layout proto) {
return new Layout(proto);
}
/** Returns the internal proto instance. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
public LayoutElementProto.Layout toProto() {
return mImpl;
}
/** Builder for {@link Layout} */
public static final class Builder {
private final LayoutElementProto.Layout.Builder mImpl =
LayoutElementProto.Layout.newBuilder();
public Builder() {}
/** Sets the root element in the layout. */
@NonNull
public Builder setRoot(@NonNull LayoutElement root) {
mImpl.setRoot(root.toLayoutElementProto());
@Nullable Fingerprint fingerprint = root.getFingerprint();
if (fingerprint != null) {
mImpl.setFingerprint(
TreeFingerprint.newBuilder().setRoot(fingerprintToProto(fingerprint)));
}
return this;
}
private static FingerprintProto.NodeFingerprint fingerprintToProto(
Fingerprint fingerprint) {
FingerprintProto.NodeFingerprint.Builder builder =
FingerprintProto.NodeFingerprint.newBuilder();
if (fingerprint.selfTypeValue() != 0) {
builder.setSelfTypeValue(fingerprint.selfTypeValue());
}
if (fingerprint.selfPropsValue() != 0) {
builder.setSelfPropsValue(fingerprint.selfPropsValue());
}
if (fingerprint.childNodesValue() != 0) {
builder.setChildNodesValue(fingerprint.childNodesValue());
}
for (Fingerprint childNode : fingerprint.childNodes()) {
builder.addChildNodes(fingerprintToProto(childNode));
}
return builder.build();
}
/** Builds an instance from accumulated values. */
@NonNull
public Layout build() {
return Layout.fromProto(mImpl.build());
}
}
}
/** The horizontal alignment of an element within its container. */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({
HORIZONTAL_ALIGN_UNDEFINED,
HORIZONTAL_ALIGN_LEFT,
HORIZONTAL_ALIGN_CENTER,
HORIZONTAL_ALIGN_RIGHT,
HORIZONTAL_ALIGN_START,
HORIZONTAL_ALIGN_END
})
@Retention(RetentionPolicy.SOURCE)
public @interface HorizontalAlignment {}
/** Horizontal alignment is undefined. */
public static final int HORIZONTAL_ALIGN_UNDEFINED = 0;
/** Horizontally align to the left. */
public static final int HORIZONTAL_ALIGN_LEFT = 1;
/** Horizontally align to center. */
public static final int HORIZONTAL_ALIGN_CENTER = 2;
/** Horizontally align to the right. */
public static final int HORIZONTAL_ALIGN_RIGHT = 3;
/** Horizontally align to the content start (left in LTR layouts, right in RTL layouts). */
public static final int HORIZONTAL_ALIGN_START = 4;
/** Horizontally align to the content end (right in LTR layouts, left in RTL layouts). */
public static final int HORIZONTAL_ALIGN_END = 5;
/** The vertical alignment of an element within its container. */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({
VERTICAL_ALIGN_UNDEFINED,
VERTICAL_ALIGN_TOP,
VERTICAL_ALIGN_CENTER,
VERTICAL_ALIGN_BOTTOM
})
@Retention(RetentionPolicy.SOURCE)
public @interface VerticalAlignment {}
/** Vertical alignment is undefined. */
public static final int VERTICAL_ALIGN_UNDEFINED = 0;
/** Vertically align to the top. */
public static final int VERTICAL_ALIGN_TOP = 1;
/** Vertically align to center. */
public static final int VERTICAL_ALIGN_CENTER = 2;
/** Vertically align to the bottom. */
public static final int VERTICAL_ALIGN_BOTTOM = 3;
/** Alignment of a text element. */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({TEXT_ALIGN_UNDEFINED, TEXT_ALIGN_START, TEXT_ALIGN_CENTER, TEXT_ALIGN_END})
@Retention(RetentionPolicy.SOURCE)
public @interface TextAlignment {}
/** Alignment is undefined. */
public static final int TEXT_ALIGN_UNDEFINED = 0;
/**
* Align to the "start" of the {@link androidx.wear.protolayout.LayoutElementBuilders.Text}
* element (left in LTR layouts, right in RTL layouts).
*/
public static final int TEXT_ALIGN_START = 1;
/**
* Align to the center of the {@link androidx.wear.protolayout.LayoutElementBuilders.Text}
* element.
*/
public static final int TEXT_ALIGN_CENTER = 2;
/**
* Align to the "end" of the {@link androidx.wear.protolayout.LayoutElementBuilders.Text}
* element (right in LTR layouts, left in RTL layouts).
*/
public static final int TEXT_ALIGN_END = 3;
/**
* The anchor position of an {@link androidx.wear.protolayout.LayoutElementBuilders.Arc}'s
* elements. This is used to specify how elements added to an {@link
* androidx.wear.protolayout.LayoutElementBuilders.Arc} should be laid out with respect to
* anchor_angle.
*
* <p>As an example, assume that the following diagrams are wrapped to an arc, and each
* represents an {@link androidx.wear.protolayout.LayoutElementBuilders.Arc} element containing
* a single {@link androidx.wear.protolayout.LayoutElementBuilders.Text} element. The {@link
* androidx.wear.protolayout.LayoutElementBuilders.Text} element's anchor_angle is "0" for all
* cases.
*
* <pre>{@code
* ARC_ANCHOR_START:
* -180 0 180
* Hello World!
*
*
* ARC_ANCHOR_CENTER:
* -180 0 180
* Hello World!
*
* ARC_ANCHOR_END:
* -180 0 180
* Hello World!
*
* }</pre>
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({ARC_ANCHOR_UNDEFINED, ARC_ANCHOR_START, ARC_ANCHOR_CENTER, ARC_ANCHOR_END})
@Retention(RetentionPolicy.SOURCE)
public @interface ArcAnchorType {}
/** Anchor position is undefined. */
public static final int ARC_ANCHOR_UNDEFINED = 0;
/**
* Anchor at the start of the elements. This will cause elements added to an arc to begin at the
* given anchor_angle, and sweep around to the right.
*/
public static final int ARC_ANCHOR_START = 1;
/**
* Anchor at the center of the elements. This will cause the center of the whole set of elements
* added to an arc to be pinned at the given anchor_angle.
*/
public static final int ARC_ANCHOR_CENTER = 2;
/**
* Anchor at the end of the elements. This will cause the set of elements inside the arc to end
* at the specified anchor_angle, i.e. all elements should be to the left of anchor_angle.
*/
public static final int ARC_ANCHOR_END = 3;
/**
* How to lay out components in a {@link androidx.wear.protolayout.LayoutElementBuilders.Arc}
* context when they are smaller than their container. This would be similar to {@code
* HorizontalAlignment} in a {@link androidx.wear.protolayout.LayoutElementBuilders.Box} or
* {@link androidx.wear.protolayout.LayoutElementBuilders.Column}.
*
* @since 1.2
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({
ANGULAR_ALIGNMENT_UNDEFINED,
ANGULAR_ALIGNMENT_START,
ANGULAR_ALIGNMENT_CENTER,
ANGULAR_ALIGNMENT_END
})
@Retention(RetentionPolicy.SOURCE)
public @interface AngularAlignment {}
/**
* Angular alignment is undefined.
*
* @since 1.2
*/
public static final int ANGULAR_ALIGNMENT_UNDEFINED = 0;
/**
* Align to the start of the container. As an example, if the container starts at 90 degrees and
* has 180 degrees of sweep, the element within would draw from 90 degrees, clockwise.
*
* @since 1.2
*/
public static final int ANGULAR_ALIGNMENT_START = 1;
/**
* Align to the center of the container. As an example, if the container starts at 90 degrees,
* and has 180 degrees of sweep, and the contained element has 90 degrees of sweep, the element
* would draw between 135 and 225 degrees.
*
* @since 1.2
*/
public static final int ANGULAR_ALIGNMENT_CENTER = 2;
/**
* Align to the end of the container. As an example, if the container starts at 90 degrees and
* has 180 degrees of sweep, and the contained element has 90 degrees of sweep, the element
* would draw between 180 and 270 degrees.
*
* @since 1.2
*/
public static final int ANGULAR_ALIGNMENT_END = 3;
/** An extensible {@code HorizontalAlignment} property. */
public static final class HorizontalAlignmentProp {
private final AlignmentProto.HorizontalAlignmentProp mImpl;
@Nullable private final Fingerprint mFingerprint;
HorizontalAlignmentProp(
AlignmentProto.HorizontalAlignmentProp impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the value. Intended for testing purposes only. */
@HorizontalAlignment
public int getValue() {
return mImpl.getValue().getNumber();
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static HorizontalAlignmentProp fromProto(
@NonNull AlignmentProto.HorizontalAlignmentProp proto) {
return new HorizontalAlignmentProp(proto, null);
}
@NonNull
AlignmentProto.HorizontalAlignmentProp toProto() {
return mImpl;
}
/** Builder for {@link HorizontalAlignmentProp} */
public static final class Builder {
private final AlignmentProto.HorizontalAlignmentProp.Builder mImpl =
AlignmentProto.HorizontalAlignmentProp.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-384830516);
public Builder() {}
/** Sets the value. */
@NonNull
public Builder setValue(@HorizontalAlignment int value) {
mImpl.setValue(AlignmentProto.HorizontalAlignment.forNumber(value));
mFingerprint.recordPropertyUpdate(1, value);
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public HorizontalAlignmentProp build() {
return new HorizontalAlignmentProp(mImpl.build(), mFingerprint);
}
}
}
/** An extensible {@code VerticalAlignment} property. */
public static final class VerticalAlignmentProp {
private final AlignmentProto.VerticalAlignmentProp mImpl;
@Nullable private final Fingerprint mFingerprint;
VerticalAlignmentProp(
AlignmentProto.VerticalAlignmentProp impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the value. Intended for testing purposes only. */
@VerticalAlignment
public int getValue() {
return mImpl.getValue().getNumber();
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static VerticalAlignmentProp fromProto(
@NonNull AlignmentProto.VerticalAlignmentProp proto) {
return new VerticalAlignmentProp(proto, null);
}
@NonNull
AlignmentProto.VerticalAlignmentProp toProto() {
return mImpl;
}
/** Builder for {@link VerticalAlignmentProp} */
public static final class Builder {
private final AlignmentProto.VerticalAlignmentProp.Builder mImpl =
AlignmentProto.VerticalAlignmentProp.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(1443510393);
public Builder() {}
/** Sets the value. */
@NonNull
public Builder setValue(@VerticalAlignment int value) {
mImpl.setValue(AlignmentProto.VerticalAlignment.forNumber(value));
mFingerprint.recordPropertyUpdate(1, value);
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public VerticalAlignmentProp build() {
return new VerticalAlignmentProp(mImpl.build(), mFingerprint);
}
}
}
/** An extensible {@code TextAlignment} property. */
public static final class TextAlignmentProp {
private final AlignmentProto.TextAlignmentProp mImpl;
@Nullable private final Fingerprint mFingerprint;
TextAlignmentProp(
AlignmentProto.TextAlignmentProp impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the value. Intended for testing purposes only. */
@TextAlignment
public int getValue() {
return mImpl.getValue().getNumber();
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static TextAlignmentProp fromProto(@NonNull AlignmentProto.TextAlignmentProp proto) {
return new TextAlignmentProp(proto, null);
}
@NonNull
AlignmentProto.TextAlignmentProp toProto() {
return mImpl;
}
/** Builder for {@link TextAlignmentProp} */
public static final class Builder {
private final AlignmentProto.TextAlignmentProp.Builder mImpl =
AlignmentProto.TextAlignmentProp.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(797507251);
public Builder() {}
/** Sets the value. */
@NonNull
public Builder setValue(@TextAlignment int value) {
mImpl.setValue(AlignmentProto.TextAlignment.forNumber(value));
mFingerprint.recordPropertyUpdate(1, value);
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public TextAlignmentProp build() {
return new TextAlignmentProp(mImpl.build(), mFingerprint);
}
}
}
/** An extensible {@code ArcAnchorType} property. */
public static final class ArcAnchorTypeProp {
private final AlignmentProto.ArcAnchorTypeProp mImpl;
@Nullable private final Fingerprint mFingerprint;
ArcAnchorTypeProp(
AlignmentProto.ArcAnchorTypeProp impl, @Nullable Fingerprint fingerprint) {
this.mImpl = impl;
this.mFingerprint = fingerprint;
}
/** Gets the value. Intended for testing purposes only. */
@ArcAnchorType
public int getValue() {
return mImpl.getValue().getNumber();
}
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
public Fingerprint getFingerprint() {
return mFingerprint;
}
@NonNull
static ArcAnchorTypeProp fromProto(@NonNull AlignmentProto.ArcAnchorTypeProp proto) {
return new ArcAnchorTypeProp(proto, null);
}
@NonNull
AlignmentProto.ArcAnchorTypeProp toProto() {
return mImpl;
}
/** Builder for {@link ArcAnchorTypeProp} */
public static final class Builder {
private final AlignmentProto.ArcAnchorTypeProp.Builder mImpl =
AlignmentProto.ArcAnchorTypeProp.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(1193249074);
public Builder() {}
/** Sets the value. */
@NonNull
public Builder setValue(@ArcAnchorType int value) {
mImpl.setValue(AlignmentProto.ArcAnchorType.forNumber(value));
mFingerprint.recordPropertyUpdate(1, value);
return this;
}
/** Builds an instance from accumulated values. */
@NonNull
public ArcAnchorTypeProp build() {
return new ArcAnchorTypeProp(mImpl.build(), mFingerprint);
}
}
}
/** Font styles, currently set up to match Wear's font styling. */
public static class FontStyles {
private static final int LARGE_SCREEN_WIDTH_DP = 210;
private FontStyles() {}
private static boolean isLargeScreen(@NonNull DeviceParameters deviceParameters) {
return deviceParameters.getScreenWidthDp() >= LARGE_SCREEN_WIDTH_DP;
}
/** Font style for large display text. */
@NonNull
public static FontStyle.Builder display1(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
.setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 54 : 50));
}
/** Font style for medium display text. */
@NonNull
public static FontStyle.Builder display2(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
.setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 44 : 40));
}
/** Font style for small display text. */
@NonNull
public static FontStyle.Builder display3(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
.setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 34 : 30));
}
/** Font style for large title text. */
@NonNull
public static FontStyle.Builder title1(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
.setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 26 : 24));
}
/** Font style for medium title text. */
@NonNull
public static FontStyle.Builder title2(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
.setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 22 : 20));
}
/** Font style for small title text. */
@NonNull
public static FontStyle.Builder title3(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
.setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 18 : 16));
}
/** Font style for large body text. */
@NonNull
public static FontStyle.Builder body1(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 18 : 16));
}
/** Font style for medium body text. */
@NonNull
public static FontStyle.Builder body2(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 16 : 14));
}
/** Font style for button text. */
@NonNull
public static FontStyle.Builder button(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
.setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 16 : 14));
}
/** Font style for large caption text. */
@NonNull
public static FontStyle.Builder caption1(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 16 : 14));
}
/** Font style for medium caption text. */
@NonNull
public static FontStyle.Builder caption2(@NonNull DeviceParameters deviceParameters) {
return new FontStyle.Builder()
.setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 14 : 12));
}
}
}