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

net.peanuuutz.fork.ui.foundation.input.VisualIndication.kt Maven / Gradle / Ivy

The newest version!
/*
 * 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.foundation.input

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import net.peanuuutz.fork.ui.foundation.input.interaction.InteractionSource
import net.peanuuutz.fork.ui.foundation.input.interaction.collectFocusState
import net.peanuuutz.fork.ui.foundation.input.interaction.collectHoverState
import net.peanuuutz.fork.ui.foundation.input.interaction.collectPressState
import net.peanuuutz.fork.ui.ui.draw.ContentDrawScope
import net.peanuuutz.fork.ui.ui.modifier.Modifier
import net.peanuuutz.fork.ui.ui.modifier.ModifierNodeElement
import net.peanuuutz.fork.ui.ui.modifier.composed
import net.peanuuutz.fork.ui.ui.node.DrawModifierNode
import net.peanuuutz.fork.ui.ui.node.ModifierNode
import net.peanuuutz.fork.util.common.Color

@Stable
fun Modifier.visualIndication(
    interactionSource: InteractionSource,
    indication: VisualIndication?,
    label: Any? = null
): Modifier {
    return composed {
        val resolvedIndication = indication ?: VisualIndication.Blank
        val instance = resolvedIndication.rememberVisualIndicationInstance(
            interactionSource = interactionSource,
            label = label
        )
        this then VisualIndicationModifier(instance)
    }
}

@Stable
interface VisualIndication {
    @Composable
    fun rememberVisualIndicationInstance(
        interactionSource: InteractionSource,
        label: Any?
    ): VisualIndicationInstance

    companion object {
        @Stable
        val Blank: VisualIndication = object : VisualIndication {
            @Composable
            override fun rememberVisualIndicationInstance(
                interactionSource: InteractionSource,
                label: Any?
            ): VisualIndicationInstance {
                return VisualIndicationInstance.Blank
            }

            override fun toString(): String {
                return "VisualIndication.Blank"
            }
        }

        @Stable
        fun overlay(
            onPress: Color = Color.White.copy(alpha = 0.2f),
            onHover: Color = Color.White.copy(alpha = 0.1f),
            onFocus: Color = onHover
        ): VisualIndication {
            return OverlayVisualIndication(
                onPress = onPress,
                onHover = onHover,
                onFocus = onFocus
            )
        }
    }
}

fun interface VisualIndicationInstance {
    fun ContentDrawScope.drawIndication()

    companion object {
        val Blank: VisualIndicationInstance = object : VisualIndicationInstance {
            override fun ContentDrawScope.drawIndication() {
                drawContent()
            }

            override fun toString(): String {
                return "VisualIndicationInstance.Blank"
            }
        }
    }
}

val LocalVisualIndication = compositionLocalOf { null }

// ======== Internal ========

private data class OverlayVisualIndication(
    val onPress: Color,
    val onHover: Color,
    val onFocus: Color
) : VisualIndication {
    @Composable
    override fun rememberVisualIndicationInstance(
        interactionSource: InteractionSource,
        label: Any?
    ): VisualIndicationInstance {
        val pressState = interactionSource.collectPressState(label)
        val hoverState = interactionSource.collectHoverState(label)
        val focusState = interactionSource.collectFocusState(label)
        return remember(interactionSource) {
            Instance(
                pressStateProvider = pressState::value,
                hoverStateProvider = hoverState::value,
                focusStateProvider = focusState::value
            )
        }
    }

    private inner class Instance(
        val pressStateProvider: () -> Boolean,
        val hoverStateProvider: () -> Boolean,
        val focusStateProvider: () -> Boolean
    ) : VisualIndicationInstance {
        override fun ContentDrawScope.drawIndication() {
            drawContent()
            val color = when {
                pressStateProvider() -> onPress
                hoverStateProvider() -> onHover
                focusStateProvider() -> onFocus
                else -> Color.Transparent
            }
            drawRect(color)
        }
    }
}

private data class VisualIndicationModifier(
    val instance: VisualIndicationInstance
) : ModifierNodeElement() {
    override fun create(): VisualIndicationModifierNode {
        return VisualIndicationModifierNode(instance)
    }

    override fun update(node: VisualIndicationModifierNode) {
        node.instance = instance
    }
}

private class VisualIndicationModifierNode(
    var instance: VisualIndicationInstance
) : ModifierNode(), DrawModifierNode {
    override fun ContentDrawScope.draw() {
        with(instance) {
            drawIndication()
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy