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

net.peanuuutz.fork.ui.foundation.input.PointerIcon.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.Stable
import kotlinx.coroutines.isActive
import net.peanuuutz.fork.ui.ui.context.pointer.PointerEvent
import net.peanuuutz.fork.ui.ui.context.pointer.PointerIcon
import net.peanuuutz.fork.ui.ui.context.pointer.PointerService
import net.peanuuutz.fork.ui.ui.modifier.Modifier
import net.peanuuutz.fork.ui.ui.modifier.ModifierNodeElement
import net.peanuuutz.fork.ui.ui.modifier.input.SuspendingPointerInputModifierNode
import net.peanuuutz.fork.ui.ui.node.BranchingModifierNode
import net.peanuuutz.fork.ui.ui.node.PointerEventPass
import net.peanuuutz.fork.ui.ui.node.PointerEventPass.In
import net.peanuuutz.fork.ui.ui.node.PointerEventPass.Out
import net.peanuuutz.fork.ui.ui.node.PointerInputModifierNode
import net.peanuuutz.fork.ui.ui.node.requireOwner

@Stable
fun Modifier.pointerIconOnHover(
    pointerIcon: PointerIcon,
    overrideDescendants: Boolean = false,
    isEnabled: Boolean = true
): Modifier {
    if (!isEnabled) {
        return this
    }
    val element = PointerIconModifier(
        pointerIcon = pointerIcon,
        overrideDescendants = overrideDescendants
    )
    return this then element
}

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

private data class PointerIconModifier(
    val pointerIcon: PointerIcon,
    val overrideDescendants: Boolean
) : ModifierNodeElement() {
    override fun create(): PointerIconModifierNode {
        return PointerIconModifierNode(
            pointerIcon = pointerIcon,
            overrideDescendants = overrideDescendants
        )
    }

    override fun update(node: PointerIconModifierNode) {
        node.pointerIcon = pointerIcon
        node.overrideDescendants = overrideDescendants
    }
}

private class PointerIconModifierNode(
    var pointerIcon: PointerIcon,
    var overrideDescendants: Boolean
) : BranchingModifierNode(), PointerInputModifierNode {
    private val pointerService: PointerService?
        get() = requireOwner().pointerService

    private val pointerInputHandler: SuspendingPointerInputModifierNode = branch {
        SuspendingPointerInputModifierNode {
            receive {
                var isInside = isHovered
                while (listenerContext.isActive) {
                    val pass = if (!overrideDescendants) In else Out
                    val event = listen(pass)
                    val isInsideBefore = isInside
                    isInside = event.isCaptured
                    if (isInside && !isInsideBefore) {
                        pointerService?.pushIcon(pointerIcon)
                        isHovered = true
                    } else if (!isInside && isInsideBefore) {
                        pointerService?.popIcon()
                        isHovered = false
                    }
                }
            }
        }
    }

    private var isHovered: Boolean = false

    override fun onDetach() {
        if (isHovered) {
            isHovered = false
            pointerService?.popIcon()
        }
    }

    override fun onPointerEvent(pass: PointerEventPass, pointerEvent: PointerEvent) {
        pointerInputHandler.onPointerEvent(pass, pointerEvent)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy