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

net.peanuuutz.fork.ui.foundation.input.Focusable.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.launch
import net.peanuuutz.fork.ui.foundation.input.interaction.FocusInteraction
import net.peanuuutz.fork.ui.foundation.input.interaction.MutableInteractionSource
import net.peanuuutz.fork.ui.foundation.input.interaction.emitFocus
import net.peanuuutz.fork.ui.foundation.input.interaction.emitUnFocus
import net.peanuuutz.fork.ui.foundation.input.interaction.tryEmitUnFocus
import net.peanuuutz.fork.ui.inspection.InspectInfo
import net.peanuuutz.fork.ui.ui.context.focus.FocusMoveDirection
import net.peanuuutz.fork.ui.ui.modifier.Modifier
import net.peanuuutz.fork.ui.ui.modifier.ModifierNodeElement
import net.peanuuutz.fork.ui.ui.modifier.input.FocusChangedModifierNode
import net.peanuuutz.fork.ui.ui.modifier.input.focusTarget
import net.peanuuutz.fork.ui.ui.node.BranchingModifierNode
import net.peanuuutz.fork.ui.ui.node.FocusEventModifierNode

@Stable
fun Modifier.focusable(
    interactionSource: MutableInteractionSource,
    label: Any? = null,
    isEnabled: Boolean = true
): Modifier {
    if (!isEnabled) {
        return this
    }
    val element = FocusableModifier(
        interactionSource = interactionSource,
        label = label
    )
    return this
        .then(element)
        .focusTarget()
}

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

private data class FocusableModifier(
    val interactionSource: MutableInteractionSource,
    val label: Any?
) : ModifierNodeElement() {
    override fun create(): FocusableModifierNode {
        return FocusableModifierNode(
            interactionSource = interactionSource,
            label = label
        )
    }

    override fun update(node: FocusableModifierNode) {
        node.interactionSource = interactionSource
        node.label = label
    }

    override fun InspectInfo.inspect() {
        set("label", label)
    }
}

private class FocusableModifierNode(
    interactionSource: MutableInteractionSource,
    label: Any?
) : BranchingModifierNode(), FocusEventModifierNode {
    var interactionSource: MutableInteractionSource = interactionSource
        set(value) {
            if (field == value) {
                return
            }
            field.tryEmitUnFocus(
                stateProvider = this::focusState,
                stateUpdater = this::focusState::set
            )
            field = value
        }

    var label: Any? = label
        set(value) {
            if (field == value) {
                return
            }
            interactionSource.tryEmitUnFocus(
                stateProvider = this::focusState,
                stateUpdater = this::focusState::set
            )
            field = value
        }

    private val focusChangedDetector: FocusChangedModifierNode = branch {
        FocusChangedModifierNode { isFocused, _ ->
            if (isFocused) {
                nodeScope.launch {
                    val effectInteractionSource = [email protected]
                    effectInteractionSource.emitUnFocus(
                        stateProvider = this@FocusableModifierNode::focusState,
                        stateUpdater = this@FocusableModifierNode::focusState::set
                    )
                    effectInteractionSource.emitFocus(
                        stateProvider = this@FocusableModifierNode::focusState,
                        stateUpdater = this@FocusableModifierNode::focusState::set,
                        labelProvider = this@FocusableModifierNode::label
                    )
                }
            } else {
                nodeScope.launch {
                    [email protected](
                        stateProvider = this@FocusableModifierNode::focusState,
                        stateUpdater = this@FocusableModifierNode::focusState::set
                    )
                }
            }
        }
    }

    private var focusState: FocusInteraction.Focus? = null

    override fun onDetach() {
        interactionSource.tryEmitUnFocus(
            stateProvider = this::focusState,
            stateUpdater = this::focusState::set
        )
    }

    override fun onFocusEvent(isFocused: Boolean, direction: FocusMoveDirection?) {
        focusChangedDetector.onFocusEvent(isFocused, direction)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy