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

commonMain.io.github.lyxnx.compose.pine.Checkbox.kt Maven / Gradle / Ivy

package io.github.lyxnx.compose.pine

import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.selection.triStateToggleable
import androidx.compose.material3.Icon
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import compose.icons.TablerIcons
import compose.icons.tablericons.Outline
import compose.icons.tablericons.outline.Check
import compose.icons.tablericons.outline.Minus
import io.github.lyxnx.compose.ui.dropShadow
import io.github.lyxnx.compose.ui.ifTrue

/**
 * A Pine Theme dual state checkbox (on/off)
 *
 * @param checked whether this checkbox is checked
 * @param onCheckChanged action to perform when clicking - this is passed the inverse of the [checked] parameter
 * @param modifier the modifier to be applied to the checkbox
 * @param enabled whether the checkbox is enabled
 * @param colors any custom colors to be used for the checkbox
 * @param interactionSource interaction source used to dispatch interaction events to
 */
@Composable
public fun Checkbox(
    checked: Boolean,
    onCheckChanged: ((Boolean) -> Unit)?,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    colors: CheckboxColors = CheckboxDefaults.checkboxColors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
    val state = remember(checked) {
        if (checked) ToggleableState.On else ToggleableState.Off
    }

    TriStateCheckbox(
        state = state,
        onClick = if (onCheckChanged != null) {
            {
                onCheckChanged(!checked)
            }
        } else null,
        modifier = modifier,
        enabled = enabled,
        colors = colors,
        interactionSource = interactionSource
    )
}

/**
 * A Pine Theme tri state checkbox
 *
 * @param state the checkbox state
 * @param onClick action to perform when clicking
 * @param modifier the modifier to be applied to the checkbox
 * @param enabled whether the checkbox is enabled
 * @param colors any custom colors to be used for the checkbox
 * @param interactionSource interaction source used to dispatch interaction events to
 */
@Composable
public fun TriStateCheckbox(
    state: ToggleableState,
    onClick: (() -> Unit)?,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    colors: CheckboxColors = CheckboxDefaults.triStateCheckboxColors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
    val isFocused by interactionSource.collectIsFocusedAsState()

    val backgroundColor by colors.backgroundColor(interactionSource, enabled, state)
    val borderColor by colors.borderColor(interactionSource, enabled, state)
    val indicatorColor by colors.indicatorColor(interactionSource, enabled, state)
    val focusShadowColor by colors.shadowColor(state)

    val transition = updateTransition(state)
    val checkDrawFraction = transition.animateFloat(
        transitionSpec = {
            when {
                initialState == ToggleableState.Off -> tween(100)
                targetState == ToggleableState.Off -> snap(100)
                else -> spring()
            }
        }
    ) {
        when (it) {
            ToggleableState.On -> 0.75f
            ToggleableState.Off -> 0f
            ToggleableState.Indeterminate -> 0.75f
        }
    }

    Box(
        modifier
            .ifTrue(onClick != null) {
                Modifier.minimumInteractiveComponentSize()
            }
            .ifTrue(isFocused) {
                dropShadow(
                    color = focusShadowColor,
                    shape = PineTheme.shapes.small,
                    spread = 2.dp
                )
            }
            .clip(PineTheme.shapes.small)
            .ifTrue(onClick != null) {
                triStateToggleable(
                    state = state,
                    onClick = onClick!!,
                    enabled = enabled,
                    role = Role.Checkbox,
                    interactionSource = interactionSource,
                    indication = LocalIndication.current
                )
            }
            .border(1.dp, borderColor, PineTheme.shapes.small)
            .background(backgroundColor)
            .requiredSize(24.dp),
        contentAlignment = Alignment.Center
    ) {
        if (state == ToggleableState.On || state == ToggleableState.Indeterminate) {
            Icon(
                painter = rememberVectorPainter(if (state == ToggleableState.On) TablerIcons.Outline.Check else TablerIcons.Outline.Minus),
                contentDescription = null,
                tint = indicatorColor,
                modifier = Modifier.fillMaxSize(checkDrawFraction.value)
            )
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy