All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.peanuuutz.fork.ui.preset.Button.kt Maven / Gradle / Ivy

/*
 * Copyright 2020 The Android Open Source Project
 * Modifications Copyright 2022 Peanuuutz
 *
 * 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 net.peanuuutz.fork.ui.preset

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import kotlinx.coroutines.CoroutineScope
import net.peanuuutz.fork.ui.foundation.draw.BorderStroke
import net.peanuuutz.fork.ui.foundation.draw.background
import net.peanuuutz.fork.ui.foundation.draw.border
import net.peanuuutz.fork.ui.foundation.draw.clip
import net.peanuuutz.fork.ui.foundation.input.LocalVisualIndication
import net.peanuuutz.fork.ui.foundation.input.clickable
import net.peanuuutz.fork.ui.foundation.input.interaction.MutableInteractionSource
import net.peanuuutz.fork.ui.foundation.input.interaction.collectFocusState
import net.peanuuutz.fork.ui.foundation.input.interaction.collectHoverState
import net.peanuuutz.fork.ui.foundation.layout.Arrangement
import net.peanuuutz.fork.ui.foundation.layout.PaddingValues
import net.peanuuutz.fork.ui.foundation.layout.list.Row
import net.peanuuutz.fork.ui.foundation.layout.list.RowScope
import net.peanuuutz.fork.ui.foundation.layout.minSize
import net.peanuuutz.fork.ui.foundation.layout.padding
import net.peanuuutz.fork.ui.preset.theme.Theme
import net.peanuuutz.fork.ui.ui.draw.Painter
import net.peanuuutz.fork.ui.ui.layout.Alignment
import net.peanuuutz.fork.ui.ui.modifier.Modifier
import net.peanuuutz.fork.ui.ui.unit.IntSize
import net.peanuuutz.fork.util.common.Color

@Composable
fun Button(
    onClick: CoroutineScope.() -> Unit,
    modifier: Modifier = Modifier,
    isEnabled: Boolean = true,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    buttonStyle: ButtonStyle = Theme.button,
    contentPadding: PaddingValues = ButtonDefaults.CommonPadding,
    contentArrangement: Arrangement.Horizontal = Arrangement.Center,
    content: @Composable RowScope.() -> Unit
) {
    val isHovered by interactionSource.collectHoverState()
    val isFocused by interactionSource.collectFocusState()
    val isSelected = isHovered || isFocused
    val border by buttonStyle.border(
        isEnabled = isEnabled,
        isSelected = isSelected
    )
    val background by buttonStyle.background(
        isEnabled = isEnabled,
        isSelected = isSelected
    )

    Row(
        modifier = modifier
            .minSize(ButtonDefaults.MinSize)
            .clickable(
                interactionSource = interactionSource,
                indication = LocalVisualIndication.current,
                isEnabled = isEnabled,
                onClick = onClick
            )
            .border(border)
            .background(background)
            .padding(contentPadding)
            .clip(),
        horizontalArrangement = contentArrangement,
        verticalAlignment = Alignment.CenterVertically
    ) {
        val contentColor by buttonStyle.content(isEnabled = isEnabled)

        CompositionLocalProvider(
            LocalContentColor provides contentColor
        ) {
            content()
        }
    }
}

object ButtonDefaults {
    val MinSize: IntSize = IntSize(12, 12)

    const val DefaultHeight: Int = 20

    val CommonPadding: PaddingValues = PaddingValues(4, 1)

    val IconPadding: PaddingValues = PaddingValues(2)
}

@Stable
interface ButtonStyle {
    @Composable
    fun border(
        isEnabled: Boolean,
        isSelected: Boolean
    ): State

    @Composable
    fun background(
        isEnabled: Boolean,
        isSelected: Boolean
    ): State

    @Composable
    fun content(isEnabled: Boolean): State

    @Stable
    abstract class Delegated(
        val delegate: ButtonStyle
    ) : ButtonStyle by delegate
}

val Theme.button: ButtonStyle
    @ReadOnlyComposable
    @Composable
    get() = LocalButton.current

@NonRestartableComposable
@Composable
fun ButtonStyleProvider(
    buttonStyle: ButtonStyle,
    content: @Composable () -> Unit
) {
    CompositionLocalProvider(
        LocalButton provides buttonStyle,
        content = content
    )
}

@Stable
class DefaultButtonStyle(
    val border: BorderStroke,
    val background: Painter,
    val contentColor: Color,
    val selectedBorder: BorderStroke,
    val selectedBackground: Painter,
    val disabledBorder: BorderStroke,
    val disabledBackground: Painter,
    val disabledContentColor: Color
) : ButtonStyle {
    @Composable
    override fun border(isEnabled: Boolean, isSelected: Boolean): State {
        val borderStroke = when {
            !isEnabled -> disabledBorder
            !isSelected -> border
            else -> selectedBorder
        }
        return rememberUpdatedState(borderStroke)
    }

    @Composable
    override fun background(isEnabled: Boolean, isSelected: Boolean): State {
        val painter = when {
            !isEnabled -> disabledBackground
            !isSelected -> background
            else -> selectedBackground
        }
        return rememberUpdatedState(painter)
    }

    @Composable
    override fun content(isEnabled: Boolean): State {
        val color = if (isEnabled) contentColor else disabledContentColor
        return rememberUpdatedState(color)
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is DefaultButtonStyle) return false
        if (border != other.border) return false
        if (background != other.background) return false
        if (contentColor != other.contentColor) return false
        if (selectedBorder != other.selectedBorder) return false
        if (selectedBackground != other.selectedBackground) return false
        if (disabledBorder != other.disabledBorder) return false
        if (disabledBackground != other.disabledBackground) return false
        if (disabledContentColor != other.disabledContentColor) return false
        return true
    }

    override fun hashCode(): Int {
        var result = border.hashCode()
        result = 31 * result + background.hashCode()
        result = 31 * result + contentColor.hashCode()
        result = 31 * result + selectedBorder.hashCode()
        result = 31 * result + selectedBackground.hashCode()
        result = 31 * result + disabledBorder.hashCode()
        result = 31 * result + disabledBackground.hashCode()
        result = 31 * result + disabledContentColor.hashCode()
        return result
    }

    override fun toString(): String {
        return "DefaultButtonStyle(border=$border, background=$background, " +
                "contentColor=$contentColor, " +
                "selectedBorder=$selectedBorder, selectedBackground=$selectedBackground, " +
                "disabledBorder=$disabledBorder, disabledBackground=$disabledBackground, " +
                "disabledContentColor=$disabledContentColor)"
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy