Shapes.kt

/*
 * Copyright 2023 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.compose.material3

import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.dp

/**
 * Material surfaces can be displayed in different shapes. Shapes direct attention, identify
 * components, communicate state, and express brand.
 *
 * The shape scale defines the style of container, offering a range of
 * curved shapes (mostly polygonal). The default [Shapes] theme for Material3 is rounded rectangles,
 * with various degrees of corner roundness:
 *
 * - None
 * - Extra Small
 * - Small
 * - Medium
 * - Large
 * - Full
 *
 * You can customize the shape system for all components in the [MaterialTheme] or you can do it
 * on a per component basis by overriding the shape parameter for that
 * component. For example, by default, buttons use the shape style “full.” If your product requires
 * a smaller amount of roundness, you can override the shape parameter with a different shape
 * value like [Shapes.small].
 *
 * TODO(b/273226734) Review documentation with references to components that use the shape themes.
 *
 * @param none By default, provides [ShapeDefaults.None], which is [RectangleShape]
 * @param extraSmall By default, provides [ShapeDefaults.ExtraSmall], a [RoundedCornerShape]
 * with 4dp [CornerSize] (used by bundled Cards).
 * @param small By default, provides [ShapeDefaults.Small], a [RoundedCornerShape]
 * with 8dp [CornerSize].
 * @param medium By default, provides [ShapeDefaults.Medium], a [RoundedCornerShape] with
 * 16dp [CornerSize] (used by shape-shifting Buttons and rounded rectangle buttons).
 * @param large By default, provides [ShapeDefaults.Large], a [RoundedCornerShape]
 * with 24dp [CornerSize] (used by Cards).
 * @param extraLarge By default, provides [ShapeDefaults.ExtraLarge], a
 * [RoundedCornerShape] with 32dp [CornerSize].
 * @param full By default, provides [ShapeDefaults.Full], a Stadium shape with
 * 50% rounded [CornerSize] (used by Button).
 */
@Immutable
class Shapes(
    val none: Shape = ShapeDefaults.None,
    val extraSmall: Shape = ShapeDefaults.ExtraSmall,
    val small: Shape = ShapeDefaults.Small,
    val medium: Shape = ShapeDefaults.Medium,
    val large: Shape = ShapeDefaults.Large,
    val extraLarge: Shape = ShapeDefaults.ExtraLarge,
    val full: Shape = ShapeDefaults.Full,
) {
    /** Returns a copy of this Shapes, optionally overriding some of the values. */
    fun copy(
        none: Shape = this.none,
        extraSmall: Shape = this.extraSmall,
        small: Shape = this.small,
        medium: Shape = this.medium,
        large: Shape = this.large,
        extraLarge: Shape = this.extraLarge,
        full: Shape = this.full
    ): Shapes = Shapes(
        none = none,
        extraSmall = extraSmall,
        small = small,
        medium = medium,
        large = large,
        extraLarge = extraLarge,
        full = full,
    )

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Shapes) return false
        if (none != other.none) return false
        if (extraSmall != other.extraSmall) return false
        if (small != other.small) return false
        if (medium != other.medium) return false
        if (large != other.large) return false
        if (extraLarge != other.extraLarge) return false
        if (full != other.full) return false
        return true
    }

    override fun hashCode(): Int {
        var result = none.hashCode()
        result = 31 * result + extraSmall.hashCode()
        result = 31 * result + small.hashCode()
        result = 31 * result + medium.hashCode()
        result = 31 * result + large.hashCode()
        result = 31 * result + extraLarge.hashCode()
        result = 31 * result + full.hashCode()
        return result
    }

    override fun toString(): String {
        return "Shapes(" +
            "none=$none, " +
            "extraSmall=$extraSmall, " +
            "small=$small, " +
            "medium=$medium, " +
            "large=$large, " +
            "extraLarge=$extraLarge, " +
            "full=$full)"
    }
}

/**
 * Contains the default values used by [Shapes]
 */
object ShapeDefaults {
    /** None provides a RectangleShape */
    val None = RectangleShape

    /** Extra small sized corner shape */
    val ExtraSmall = RoundedCornerShape(corner = CornerSize(4.dp))

    /** Small sized corner shape */
    val Small = RoundedCornerShape(corner = CornerSize(8.dp))

    /** Medium sized corner shape */
    val Medium = RoundedCornerShape(corner = CornerSize(16.dp))

    /** Large sized corner shape */
    val Large = RoundedCornerShape(corner = CornerSize(24.dp))

    /** Extra large sized corner shape */
    val ExtraLarge = RoundedCornerShape(corner = CornerSize(32.dp))

    /** Full provides a stadium-shape with 50% rounded corners */
    val Full = RoundedCornerShape(corner = CornerSize(50))
}

/** CompositionLocal used to specify the default shapes for the surfaces. */
internal val LocalShapes = staticCompositionLocalOf { Shapes() }