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

commonMain.androidx.compose.foundation.text.input.TextFieldCharSequence.kt Maven / Gradle / Ivy

/*
 * Copyright 2024 The Android Open Source Project
 *
 * 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 androidx.compose.foundation.text.input

import androidx.compose.foundation.text.input.internal.toCharArray
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.coerceIn
import kotlin.jvm.JvmInline

internal typealias PlacedAnnotation = AnnotatedString.Range

/**
 * An immutable snapshot of the contents of a [TextFieldState].
 *
 * This class is a [CharSequence] and directly represents the text being edited. It also stores the
 * current [selection] of the field, which may either represent the cursor (if the selection is
 * [collapsed][TextRange.collapsed]) or the selection range.
 *
 * This class also may contain the range being composed by the IME, if any, although this is not
 * exposed.
 *
 * @param text If this TextFieldCharSequence is actually a copy of another, make sure to use the
 *   backing CharSequence object to stop unnecessary nesting and logic that depends on exact
 *   equality of CharSequence comparison that's using [CharSequence.equals].
 * @see TextFieldBuffer
 */
internal class TextFieldCharSequence(
    text: CharSequence = "",
    selection: TextRange = TextRange.Zero,
    composition: TextRange? = null,
    highlight: Pair? = null,
    val composingAnnotations: List? = null
) : CharSequence {

    override val length: Int
        get() = text.length

    val text: CharSequence = if (text is TextFieldCharSequence) text.text else text

    /**
     * The selection range. If the selection is collapsed, it represents cursor location. When
     * selection range is out of bounds, it is constrained with the text length.
     */
    val selection: TextRange = selection.coerceIn(0, text.length)

    /**
     * Composition range created by IME. If null, there is no composition range.
     *
     * Input service composition is an instance of text produced by IME. An example visual for the
     * composition is that the currently composed word is visually separated from others with
     * underline, or text background. For description of composition please check
     * [W3C IME Composition](https://www.w3.org/TR/ime-api/#ime-composition)
     *
     * Composition can only be set by the system.
     */
    val composition: TextRange? = composition?.coerceIn(0, text.length)

    /**
     * Range of text to be highlighted. This may be used to display handwriting gesture previews
     * from the IME.
     */
    val highlight: Pair? =
        highlight?.copy(second = highlight.second.coerceIn(0, text.length))

    override operator fun get(index: Int): Char = text[index]

    override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
        text.subSequence(startIndex, endIndex)

    override fun toString(): String = text.toString()

    fun contentEquals(other: CharSequence): Boolean = text.contentEquals(other)

    /**
     * Copies the contents of this sequence from [[sourceStartIndex], [sourceEndIndex]) into
     * [destination] starting at [destinationOffset].
     */
    fun toCharArray(
        destination: CharArray,
        destinationOffset: Int,
        sourceStartIndex: Int,
        sourceEndIndex: Int
    ) {
        text.toCharArray(destination, destinationOffset, sourceStartIndex, sourceEndIndex)
    }

    /**
     * Whether to show the cursor or selection and associated handles. When there is a handwriting
     * gesture preview highlight, the cursor or selection should be hidden.
     */
    fun shouldShowSelection(): Boolean = highlight == null

    /**
     * Returns true if [other] is a [TextFieldCharSequence] with the same contents, text, and
     * composition. To compare just the text, call [contentEquals].
     */
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other === null) return false
        if (this::class != other::class) return false

        other as TextFieldCharSequence

        if (selection != other.selection) return false
        if (composition != other.composition) return false
        if (highlight != other.highlight) return false
        if (composingAnnotations != other.composingAnnotations) return false
        if (!contentEquals(other.text)) return false

        return true
    }

    override fun hashCode(): Int {
        var result = text.hashCode()
        result = 31 * result + selection.hashCode()
        result = 31 * result + (composition?.hashCode() ?: 0)
        result = 31 * result + highlight.hashCode()
        result = 31 * result + composingAnnotations.hashCode()
        return result
    }
}

/** A text range highlight type. The highlight styling depends on the type. */
@JvmInline
internal value class TextHighlightType private constructor(private val value: Int) {
    companion object {
        /**
         * A highlight which previews the text range which would be selected by an ongoing stylus
         * handwriting select gesture.
         */
        val HandwritingSelectPreview = TextHighlightType(0)

        /**
         * A highlight which previews the text range which would be deleted by an ongoing stylus
         * handwriting delete gesture.
         */
        val HandwritingDeletePreview = TextHighlightType(1)
    }
}

/**
 * Returns the text before the selection.
 *
 * @param maxChars maximum number of characters (inclusive) before the minimum value in
 *   [TextFieldCharSequence.selection].
 * @see TextRange.min
 */
internal fun TextFieldCharSequence.getTextBeforeSelection(maxChars: Int): CharSequence =
    subSequence(kotlin.math.max(0, selection.min - maxChars), selection.min)

/**
 * Returns the text after the selection.
 *
 * @param maxChars maximum number of characters (exclusive) after the maximum value in
 *   [TextFieldCharSequence.selection].
 * @see TextRange.max
 */
internal fun TextFieldCharSequence.getTextAfterSelection(maxChars: Int): CharSequence =
    subSequence(selection.max, kotlin.math.min(selection.max + maxChars, length))

/** Returns the currently selected text. */
internal fun TextFieldCharSequence.getSelectedText(): CharSequence =
    subSequence(selection.min, selection.max)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy