ColorBuilders.java

/*
 * Copyright 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.graphics.Color;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.wear.protolayout.expression.DynamicBuilders;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor;
import androidx.wear.protolayout.expression.Fingerprint;
import androidx.wear.protolayout.proto.ColorProto;

/** Builders for color utilities for layout elements. */
public final class ColorBuilders {
    private ColorBuilders() {}

    /**
     * Shortcut for building a {@link ColorProp} using an ARGB value.
     *
     * @since 1.0
     */
    @NonNull
    public static ColorProp argb(@ColorInt int colorArgb) {
        return new ColorProp.Builder().setArgb(colorArgb).build();
    }

    /**
     * A property defining a color.
     *
     * @since 1.0
     */
    public static final class ColorProp {
        private final ColorProto.ColorProp mImpl;
        @Nullable private final Fingerprint mFingerprint;

        ColorProp(ColorProto.ColorProp impl, @Nullable Fingerprint fingerprint) {
            this.mImpl = impl;
            this.mFingerprint = fingerprint;
        }

        /**
         * Gets the static color value, in ARGB format. If a dynamic value is also set and the
         * renderer supports dynamic values for the corresponding field, this static value will be
         * ignored. If the static value is not specified, zero (equivalent to {@link
         * Color#TRANSPARENT}) will be used instead.
         *
         * @since 1.0
         */
        @ColorInt
        public int getArgb() {
            return mImpl.getArgb();
        }

        /**
         * Gets the dynamic value. Note that when setting this value, the static value is still
         * required to be set to support older renderers that only read the static value. If {@code
         * dynamicValue} has an invalid result, the provided static value will be used instead.
         *
         * @since 1.2
         */
        @Nullable
        public DynamicColor getDynamicValue() {
            if (mImpl.hasDynamicValue()) {
                return DynamicBuilders.dynamicColorFromProto(mImpl.getDynamicValue());
            } else {
                return null;
            }
        }

        /** 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 ColorProp fromProto(
                @NonNull ColorProto.ColorProp proto, @Nullable Fingerprint fingerprint) {
            return new ColorProp(proto, fingerprint);
        }

        @NonNull
        static ColorProp fromProto(@NonNull ColorProto.ColorProp proto) {
            return fromProto(proto, null);
        }

        /** Returns the internal proto instance. */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public ColorProto.ColorProp toProto() {
            return mImpl;
        }

        @Override
        @NonNull
        public String toString() {
            return "ColorProp{" + "argb=" + getArgb() + ", dynamicValue=" + getDynamicValue() + "}";
        }

        /** Builder for {@link ColorProp} */
        public static final class Builder {
            private final ColorProto.ColorProp.Builder mImpl = ColorProto.ColorProp.newBuilder();
            private final Fingerprint mFingerprint = new Fingerprint(-1955659823);

            /**
             * @deprecated Use {@link #Builder(int)} instead.
             */
            @Deprecated
            public Builder() {}

            public Builder(@ColorInt int argb) {
                setArgb(argb);
            }

            /**
             * Sets the static  color value, in ARGB format. If a dynamic value is also set and the
             * renderer supports dynamic values for the corresponding field, this static value
             * will be ignored. If the static value is not specified, zero (equivalent to {@link
             * Color#TRANSPARENT}) will be used instead.
             *
             * @since 1.0
             */
            @NonNull
            public Builder setArgb(@ColorInt int argb) {
                mImpl.setArgb(argb);
                mFingerprint.recordPropertyUpdate(1, argb);
                return this;
            }

            /**
             * Sets the dynamic value. Note that when setting this value, the static value is still
             * required to be set to support older renderers that only read the static value. If
             * {@code dynamicValue} has an invalid result, the provided static value will be used
             * instead.
             *
             * @since 1.2
             */
            @NonNull
            public Builder setDynamicValue(@NonNull DynamicColor dynamicValue) {
                mImpl.setDynamicValue(dynamicValue.toDynamicColorProto());
                mFingerprint.recordPropertyUpdate(
                        2, checkNotNull(dynamicValue.getFingerprint()).aggregateValueAsInt());
                return this;
            }

            /**
             * Builds an instance from accumulated values.
             *
             * @throws IllegalStateException if a dynamic value is set using {@link
             *     #setDynamicValue(DynamicColor)} but neither {@link #Builder(int)} nor {@link
             *     #setArgb(int)} is used to provide a static value.
             */
            @NonNull
            public ColorProp build() {
                if (mImpl.hasDynamicValue() && !mImpl.hasArgb()) {
                    throw new IllegalStateException("Static value is missing.");
                }
                return new ColorProp(mImpl.build(), mFingerprint);
            }
        }
    }
}