SurfaceDefaults.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.tv.material3

import androidx.annotation.FloatRange
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.dp

/**
 * Contains the default values used by a non-interactive [Surface]
 */
@ExperimentalTvMaterial3Api
object NonInteractiveSurfaceDefaults {
    /**
     * Represents the default shape used by a non-interactive [Surface]
     */
    val shape: Shape @ReadOnlyComposable @Composable get() = MaterialTheme.shapes.medium

    /**
     * Creates a [NonInteractiveSurfaceColors] that represents the default container & content
     * colors used by a non-interactive [Surface].
     *
     * @param containerColor the container color of this Surface
     * @param contentColor the content color of this Surface
     */
    @ReadOnlyComposable
    @Composable
    fun colors(
        containerColor: Color = MaterialTheme.colorScheme.surface,
        contentColor: Color = contentColorFor(containerColor)
    ) = NonInteractiveSurfaceColors(
        containerColor = containerColor,
        contentColor = contentColor
    )

    /**
     * Represents the default border used by a non-interactive [Surface]
     */
    internal val border: Border = Border.None

    /**
     * Represents the default glow used by a non-interactive [Surface]
     */
    internal val glow: Glow = Glow.None
}

/**
 * Contains the default values used by clickable Surface.
 */
@ExperimentalTvMaterial3Api
object ClickableSurfaceDefaults {
    internal fun shape(
        enabled: Boolean,
        focused: Boolean,
        pressed: Boolean,
        shape: ClickableSurfaceShape
    ): Shape {
        return when {
            pressed && enabled -> shape.pressedShape
            focused && enabled -> shape.focusedShape
            focused && !enabled -> shape.focusedDisabledShape
            enabled -> shape.shape
            else -> shape.disabledShape
        }
    }

    /**
     * Creates a [ClickableSurfaceShape] that represents the default container shapes used in a
     * Surface.
     *
     * @param shape the shape used when the Surface is enabled, and has no other
     * [Interaction]s.
     * @param focusedShape the shape used when the Surface is enabled and focused.
     * @param pressedShape the shape used when the Surface is enabled pressed.
     * @param disabledShape the shape used when the Surface is not enabled.
     * @param focusedDisabledShape the shape used when the Surface is not enabled and focused.
     */
    @ReadOnlyComposable
    @Composable
    fun shape(
        shape: Shape = MaterialTheme.shapes.medium,
        focusedShape: Shape = shape,
        pressedShape: Shape = shape,
        disabledShape: Shape = shape,
        focusedDisabledShape: Shape = disabledShape
    ) = ClickableSurfaceShape(
        shape = shape,
        focusedShape = focusedShape,
        pressedShape = pressedShape,
        disabledShape = disabledShape,
        focusedDisabledShape = focusedDisabledShape
    )

    internal fun containerColor(
        enabled: Boolean,
        focused: Boolean,
        pressed: Boolean,
        colors: ClickableSurfaceColors
    ): Color {
        return when {
            pressed && enabled -> colors.pressedContainerColor
            focused && enabled -> colors.focusedContainerColor
            enabled -> colors.containerColor
            else -> colors.disabledContainerColor
        }
    }

    internal fun contentColor(
        enabled: Boolean,
        focused: Boolean,
        pressed: Boolean,
        colors: ClickableSurfaceColors
    ): Color {
        return when {
            pressed && enabled -> colors.pressedContentColor
            focused && enabled -> colors.focusedContentColor
            enabled -> colors.contentColor
            else -> colors.disabledContentColor
        }
    }

    /**
     * Creates a [ClickableSurfaceColors] that represents the default container & content colors
     * used in a Surface.
     *
     * @param containerColor the container color of this Surface when enabled
     * @param contentColor the content color of this Surface when enabled
     * @param focusedContainerColor the container color of this Surface when enabled and focused
     * @param focusedContentColor the content color of this Surface when enabled and focused
     * @param pressedContainerColor the container color of this Surface when enabled and pressed
     * @param pressedContentColor the content color of this Surface when enabled and pressed
     * @param disabledContainerColor the container color of this Surface when not enabled
     * @param disabledContentColor the content color of this Surface when not enabled
     */
    @ReadOnlyComposable
    @Composable
    fun colors(
        containerColor: Color = MaterialTheme.colorScheme.surface,
        contentColor: Color = contentColorFor(containerColor),
        focusedContainerColor: Color = MaterialTheme.colorScheme.inverseSurface,
        focusedContentColor: Color = contentColorFor(focusedContainerColor),
        pressedContainerColor: Color = focusedContainerColor,
        pressedContentColor: Color = contentColorFor(pressedContainerColor),
        disabledContainerColor: Color = MaterialTheme.colorScheme.surfaceVariant
            .copy(alpha = DisabledContainerAlpha),
        disabledContentColor: Color = MaterialTheme.colorScheme.onSurface
    ) = ClickableSurfaceColors(
        containerColor = containerColor,
        contentColor = contentColor,
        focusedContainerColor = focusedContainerColor,
        focusedContentColor = focusedContentColor,
        pressedContainerColor = pressedContainerColor,
        pressedContentColor = pressedContentColor,
        disabledContainerColor = disabledContainerColor,
        disabledContentColor = disabledContentColor
    )

    internal fun scale(
        enabled: Boolean,
        focused: Boolean,
        pressed: Boolean,
        scale: ClickableSurfaceScale
    ): Float {
        return when {
            pressed && enabled -> scale.pressedScale
            focused && enabled -> scale.focusedScale
            focused && !enabled -> scale.focusedDisabledScale
            enabled -> scale.scale
            else -> scale.disabledScale
        }
    }

    /**
     * Creates a [ClickableSurfaceScale] that represents the default scales used in a
     * Surface. scales are used to modify the size of a composable in different [Interaction]
     * states e.g. 1f (original) in default state, 1.2f (scaled up) in focused state,
     * 0.8f (scaled down) in pressed state, etc.
     *
     * @param scale the scale to be used for this Surface when enabled
     * @param focusedScale the scale to be used for this Surface when focused
     * @param pressedScale the scale to be used for this Surface when pressed
     * @param disabledScale the scale to be used for this Surface when disabled
     * @param focusedDisabledScale the scale to be used for this Surface when disabled and
     * focused
     */
    fun scale(
        @FloatRange(from = 0.0) scale: Float = 1f,
        @FloatRange(from = 0.0) focusedScale: Float = 1.1f,
        @FloatRange(from = 0.0) pressedScale: Float = scale,
        @FloatRange(from = 0.0) disabledScale: Float = scale,
        @FloatRange(from = 0.0) focusedDisabledScale: Float = disabledScale
    ) = ClickableSurfaceScale(
        scale = scale,
        focusedScale = focusedScale,
        pressedScale = pressedScale,
        disabledScale = disabledScale,
        focusedDisabledScale = focusedDisabledScale
    )

    internal fun border(
        enabled: Boolean,
        focused: Boolean,
        pressed: Boolean,
        border: ClickableSurfaceBorder
    ): Border {
        return when {
            pressed && enabled -> border.pressedBorder
            focused && enabled -> border.focusedBorder
            focused && !enabled -> border.focusedDisabledBorder
            enabled -> border.border
            else -> border.disabledBorder
        }
    }

    /**
     * Creates a [ClickableSurfaceBorder] that represents the default [Border]s applied on a
     * Surface in different [Interaction] states.
     *
     * @param border the [Border] to be used for this Surface when enabled
     * @param focusedBorder the [Border] to be used for this Surface when focused
     * @param pressedBorder the [Border] to be used for this Surface when pressed
     * @param disabledBorder the [Border] to be used for this Surface when disabled
     * @param focusedDisabledBorder the [Border] to be used for this Surface when disabled and
     * focused
     */
    @ReadOnlyComposable
    @Composable
    fun border(
        border: Border = Border.None,
        focusedBorder: Border = border,
        pressedBorder: Border = focusedBorder,
        disabledBorder: Border = border,
        focusedDisabledBorder: Border = Border(
            border = BorderStroke(
                width = 2.dp,
                color = MaterialTheme.colorScheme.border
            ),
            inset = 0.dp,
            shape = ShapeDefaults.Small
        )
    ) = ClickableSurfaceBorder(
        border = border,
        focusedBorder = focusedBorder,
        pressedBorder = pressedBorder,
        disabledBorder = disabledBorder,
        focusedDisabledBorder = focusedDisabledBorder
    )

    internal fun glow(
        enabled: Boolean,
        focused: Boolean,
        pressed: Boolean,
        glow: ClickableSurfaceGlow
    ): Glow {
        return if (enabled) {
            when {
                pressed -> glow.pressedGlow
                focused -> glow.focusedGlow
                else -> glow.glow
            }
        } else {
            Glow.None
        }
    }

    /**
     * Creates a [ClickableSurfaceGlow] that represents the default [Glow]s used in a
     * Surface.
     *
     * @param glow the Glow behind this Surface when enabled
     * @param focusedGlow the Glow behind this Surface when focused
     * @param pressedGlow the Glow behind this Surface when pressed
     */
    fun glow(
        glow: Glow = Glow.None,
        focusedGlow: Glow = glow,
        pressedGlow: Glow = glow
    ) = ClickableSurfaceGlow(
        glow = glow,
        focusedGlow = focusedGlow,
        pressedGlow = pressedGlow
    )
}

/**
 * Contains the default values used by Toggleable Surface.
 */
@ExperimentalTvMaterial3Api
object ToggleableSurfaceDefaults {
    /**
     * Creates a [ToggleableSurfaceShape] that represents the default container shapes used in a
     * toggleable Surface.
     *
     * @param shape the shape used when the Surface is enabled, and has no other
     * [Interaction]s.
     * @param focusedShape the shape used when the Surface is enabled and focused.
     * @param pressedShape the shape used when the Surface is enabled and pressed.
     * @param selectedShape the shape used when the Surface is enabled and selected.
     * @param disabledShape the shape used when the Surface is not enabled.
     * @param focusedSelectedShape the shape used when the Surface is enabled, focused and selected.
     * @param focusedDisabledShape the shape used when the Surface is not enabled and focused.
     * @param pressedSelectedShape the shape used when the Surface is enabled, pressed and selected.
     * @param selectedDisabledShape the shape used when the Surface is not enabled and selected.
     * @param focusedSelectedDisabledShape the shape used when the Surface is not enabled, focused
     * and selected.
     */
    @ReadOnlyComposable
    @Composable
    fun shape(
        shape: Shape = MaterialTheme.shapes.medium,
        focusedShape: Shape = shape,
        pressedShape: Shape = shape,
        selectedShape: Shape = shape,
        disabledShape: Shape = shape,
        focusedSelectedShape: Shape = shape,
        focusedDisabledShape: Shape = disabledShape,
        pressedSelectedShape: Shape = shape,
        selectedDisabledShape: Shape = disabledShape,
        focusedSelectedDisabledShape: Shape = disabledShape
    ) = ToggleableSurfaceShape(
        shape = shape,
        focusedShape = focusedShape,
        pressedShape = pressedShape,
        selectedShape = selectedShape,
        disabledShape = disabledShape,
        focusedSelectedShape = focusedSelectedShape,
        focusedDisabledShape = focusedDisabledShape,
        pressedSelectedShape = pressedSelectedShape,
        selectedDisabledShape = selectedDisabledShape,
        focusedSelectedDisabledShape = focusedSelectedDisabledShape
    )

    /**
     * Creates a [ToggleableSurfaceColors] that represents the default container & content colors
     * used in a toggleable Surface.
     *
     * @param containerColor the container color used when the Surface is enabled, and has no other
     * [Interaction]s.
     * @param contentColor the content color used when the Surface is enabled, and has no other
     * [Interaction]s.
     * @param focusedContainerColor the container color used when the Surface is enabled and
     * focused.
     * @param focusedContentColor the content color used when the Surface is enabled and
     * focused.
     * @param pressedContainerColor the container color used when the Surface is enabled and
     * pressed.
     * @param pressedContentColor the content color used when the Surface is enabled and
     * pressed.
     * @param selectedContainerColor the container color used when the Surface is enabled and
     * selected.
     * @param selectedContentColor the content color used when the Surface is enabled and
     * selected.
     * @param disabledContainerColor the container color used when the Surface is not enabled.
     * @param disabledContentColor the content color used when the Surface is not enabled.
     * @param focusedSelectedContainerColor the container color used when the Surface is enabled,
     * focused and selected.
     * @param focusedSelectedContentColor the content color used when the Surface is enabled,
     * focused and selected.
     * @param pressedSelectedContainerColor the container color used when the Surface is enabled,
     * pressed and selected.
     * @param pressedSelectedContentColor the content color used when the Surface is enabled,
     * pressed and selected.
     */
    @ReadOnlyComposable
    @Composable
    fun colors(
        containerColor: Color = MaterialTheme.colorScheme.surface,
        contentColor: Color = contentColorFor(containerColor),
        focusedContainerColor: Color = MaterialTheme.colorScheme.inverseSurface,
        focusedContentColor: Color = contentColorFor(focusedContainerColor),
        pressedContainerColor: Color = focusedContainerColor,
        pressedContentColor: Color = contentColorFor(pressedContainerColor),
        selectedContainerColor: Color = MaterialTheme.colorScheme.inverseSurface
            .copy(alpha = SelectedContainerAlpha),
        selectedContentColor: Color = MaterialTheme.colorScheme.inverseOnSurface,
        disabledContainerColor: Color = MaterialTheme.colorScheme.surfaceVariant
            .copy(alpha = DisabledContainerAlpha),
        disabledContentColor: Color = MaterialTheme.colorScheme.onSurface,
        focusedSelectedContainerColor: Color = MaterialTheme.colorScheme.inverseSurface
            .copy(alpha = SelectedContainerAlpha),
        focusedSelectedContentColor: Color = MaterialTheme.colorScheme.inverseOnSurface,
        pressedSelectedContainerColor: Color = focusedSelectedContainerColor,
        pressedSelectedContentColor: Color = focusedSelectedContentColor
    ) = ToggleableSurfaceColors(
        containerColor = containerColor,
        contentColor = contentColor,
        focusedContainerColor = focusedContainerColor,
        focusedContentColor = focusedContentColor,
        pressedContainerColor = pressedContainerColor,
        pressedContentColor = pressedContentColor,
        selectedContainerColor = selectedContainerColor,
        selectedContentColor = selectedContentColor,
        disabledContainerColor = disabledContainerColor,
        disabledContentColor = disabledContentColor,
        focusedSelectedContainerColor = focusedSelectedContainerColor,
        focusedSelectedContentColor = focusedSelectedContentColor,
        pressedSelectedContainerColor = pressedSelectedContainerColor,
        pressedSelectedContentColor = pressedSelectedContentColor
    )

    /**
     * Creates a [ToggleableSurfaceScale] that represents the default scales used in a
     * toggleable Surface. scales are used to modify the size of a composable in different
     * [Interaction] states e.g. 1f (original) in default state, 1.2f (scaled up) in focused state,
     * 0.8f (scaled down) in pressed state, etc.
     *
     * @param scale the scale used when the Surface is enabled, and has no other
     * [Interaction]s.
     * @param focusedScale the scale used when the Surface is enabled and focused.
     * @param pressedScale the scale used when the Surface is enabled and pressed.
     * @param selectedScale the scale used when the Surface is enabled and selected.
     * @param disabledScale the scale used when the Surface is not enabled.
     * @param focusedSelectedScale the scale used when the Surface is enabled, focused and
     * selected.
     * @param focusedDisabledScale the scale used when the Surface is not enabled and
     * focused.
     * @param pressedSelectedScale the scale used when the Surface is enabled, pressed and
     * selected.
     * @param selectedDisabledScale the scale used when the Surface is not enabled and
     * selected.
     * @param focusedSelectedDisabledScale the scale used when the Surface is not enabled,
     * focused and selected.
     */
    fun scale(
        scale: Float = 1f,
        focusedScale: Float = 1.1f,
        pressedScale: Float = scale,
        selectedScale: Float = scale,
        disabledScale: Float = scale,
        focusedSelectedScale: Float = focusedScale,
        focusedDisabledScale: Float = disabledScale,
        pressedSelectedScale: Float = scale,
        selectedDisabledScale: Float = disabledScale,
        focusedSelectedDisabledScale: Float = disabledScale
    ) = ToggleableSurfaceScale(
        scale = scale,
        focusedScale = focusedScale,
        pressedScale = pressedScale,
        selectedScale = selectedScale,
        disabledScale = disabledScale,
        focusedSelectedScale = focusedSelectedScale,
        focusedDisabledScale = focusedDisabledScale,
        pressedSelectedScale = pressedSelectedScale,
        selectedDisabledScale = selectedDisabledScale,
        focusedSelectedDisabledScale = focusedSelectedDisabledScale
    )

    /**
     * Creates a [ToggleableSurfaceBorder] that represents the default [Border]s applied on a
     * toggleable Surface in different [Interaction] states.
     *
     * @param border the [Border] used when the Surface is enabled, and has no other
     * [Interaction]s.
     * @param focusedBorder the [Border] used when the Surface is enabled and focused.
     * @param pressedBorder the [Border] used when the Surface is enabled and pressed.
     * @param selectedBorder the [Border] used when the Surface is enabled and selected.
     * @param disabledBorder the [Border] used when the Surface is not enabled.
     * @param focusedSelectedBorder the [Border] used when the Surface is enabled, focused and
     * selected.
     * @param focusedDisabledBorder the [Border] used when the Surface is not enabled and focused.
     * @param pressedSelectedBorder the [Border] used when the Surface is enabled, pressed and
     * selected.
     * @param selectedDisabledBorder the [Border] used when the Surface is not enabled and
     * selected.
     * @param focusedSelectedDisabledBorder the [Border] used when the Surface is not enabled,
     * focused and selected.
     */
    fun border(
        border: Border = Border.None,
        focusedBorder: Border = border,
        pressedBorder: Border = focusedBorder,
        selectedBorder: Border = border,
        disabledBorder: Border = border,
        focusedSelectedBorder: Border = focusedBorder,
        focusedDisabledBorder: Border = disabledBorder,
        pressedSelectedBorder: Border = border,
        selectedDisabledBorder: Border = disabledBorder,
        focusedSelectedDisabledBorder: Border = disabledBorder
    ) = ToggleableSurfaceBorder(
        border = border,
        focusedBorder = focusedBorder,
        pressedBorder = pressedBorder,
        selectedBorder = selectedBorder,
        disabledBorder = disabledBorder,
        focusedSelectedBorder = focusedSelectedBorder,
        focusedDisabledBorder = focusedDisabledBorder,
        pressedSelectedBorder = pressedSelectedBorder,
        selectedDisabledBorder = selectedDisabledBorder,
        focusedSelectedDisabledBorder = focusedSelectedDisabledBorder
    )

    /**
     * Creates a [ToggleableSurfaceGlow] that represents the default [Glow]s used in a
     * toggleable Surface.
     *
     * @param glow the [Glow] used when the Surface is enabled, and has no other [Interaction]s.
     * @param focusedGlow the [Glow] used when the Surface is enabled and focused.
     * @param pressedGlow the [Glow] used when the Surface is enabled and pressed.
     * @param selectedGlow the [Glow] used when the Surface is enabled and selected.
     * @param focusedSelectedGlow the [Glow] used when the Surface is enabled, focused and selected.
     * @param pressedSelectedGlow the [Glow] used when the Surface is enabled, pressed and selected.
     */
    fun glow(
        glow: Glow = Glow.None,
        focusedGlow: Glow = glow,
        pressedGlow: Glow = glow,
        selectedGlow: Glow = glow,
        focusedSelectedGlow: Glow = focusedGlow,
        pressedSelectedGlow: Glow = glow
    ) = ToggleableSurfaceGlow(
        glow = glow,
        focusedGlow = focusedGlow,
        pressedGlow = pressedGlow,
        selectedGlow = selectedGlow,
        focusedSelectedGlow = focusedSelectedGlow,
        pressedSelectedGlow = pressedSelectedGlow
    )

    internal fun shape(
        enabled: Boolean,
        focused: Boolean,
        pressed: Boolean,
        selected: Boolean,
        shape: ToggleableSurfaceShape
    ): Shape {
        return when {
            enabled && selected && pressed -> shape.pressedSelectedShape
            enabled && selected && focused -> shape.focusedSelectedShape
            enabled && selected -> shape.selectedShape
            enabled && pressed -> shape.pressedShape
            enabled && focused -> shape.focusedShape
            enabled -> shape.shape
            !enabled && selected && focused -> shape.focusedSelectedDisabledShape
            !enabled && selected -> shape.selectedDisabledShape
            !enabled && focused -> shape.focusedDisabledShape
            else -> shape.disabledShape
        }
    }

    internal fun containerColor(
        enabled: Boolean,
        focused: Boolean,
        pressed: Boolean,
        selected: Boolean,
        colors: ToggleableSurfaceColors
    ): Color {
        return when {
            enabled && selected && pressed -> colors.pressedSelectedContainerColor
            enabled && selected && focused -> colors.focusedSelectedContainerColor
            enabled && selected -> colors.selectedContainerColor
            enabled && pressed -> colors.pressedContainerColor
            enabled && focused -> colors.focusedContainerColor
            enabled -> colors.containerColor
            else -> colors.disabledContainerColor
        }
    }

    internal fun contentColor(
        enabled: Boolean,
        focused: Boolean,
        pressed: Boolean,
        selected: Boolean,
        colors: ToggleableSurfaceColors
    ): Color {
        return when {
            enabled && selected && pressed -> colors.pressedSelectedContentColor
            enabled && selected && focused -> colors.focusedSelectedContentColor
            enabled && selected -> colors.selectedContentColor
            enabled && pressed -> colors.pressedContentColor
            enabled && focused -> colors.focusedContentColor
            enabled -> colors.contentColor
            else -> colors.disabledContentColor
        }
    }

    internal fun scale(
        enabled: Boolean,
        focused: Boolean,
        pressed: Boolean,
        selected: Boolean,
        scale: ToggleableSurfaceScale
    ): Float {
        return when {
            enabled && selected && pressed -> scale.pressedSelectedScale
            enabled && selected && focused -> scale.focusedSelectedScale
            enabled && selected -> scale.selectedScale
            enabled && pressed -> scale.pressedScale
            enabled && focused -> scale.focusedScale
            enabled -> scale.scale
            !enabled && selected && focused -> scale.focusedSelectedDisabledScale
            !enabled && selected -> scale.selectedDisabledScale
            !enabled && focused -> scale.focusedDisabledScale
            else -> scale.disabledScale
        }
    }

    internal fun border(
        enabled: Boolean,
        focused: Boolean,
        pressed: Boolean,
        selected: Boolean,
        border: ToggleableSurfaceBorder
    ): Border {
        return when {
            enabled && selected && pressed -> border.pressedSelectedBorder
            enabled && selected && focused -> border.focusedSelectedBorder
            enabled && selected -> border.selectedBorder
            enabled && pressed -> border.pressedBorder
            enabled && focused -> border.focusedBorder
            enabled -> border.border
            !enabled && selected && focused -> border.focusedSelectedDisabledBorder
            !enabled && selected -> border.selectedDisabledBorder
            !enabled && focused -> border.focusedDisabledBorder
            else -> border.disabledBorder
        }
    }

    internal fun glow(
        enabled: Boolean,
        focused: Boolean,
        pressed: Boolean,
        selected: Boolean,
        glow: ToggleableSurfaceGlow
    ): Glow {
        return when {
            enabled && selected && pressed -> glow.pressedSelectedGlow
            enabled && selected && focused -> glow.focusedSelectedGlow
            enabled && selected -> glow.selectedGlow
            enabled && pressed -> glow.pressedGlow
            enabled && focused -> glow.focusedGlow
            enabled -> glow.glow
            else -> Glow.None
        }
    }
}

private const val DisabledContainerAlpha = 0.4f
private const val SelectedContainerAlpha = 0.5f