Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve support for BasicTextField2 on Desktop #1496

Merged
merged 12 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 The Android Open Source Project
* Copyright 2023 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.
Expand All @@ -17,14 +17,18 @@
package androidx.compose.foundation.text.input.internal

import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
import androidx.compose.ui.platform.PlatformTextInputMethodRequest
import androidx.compose.ui.platform.PlatformTextInputSession
import androidx.compose.ui.platform.TextFieldStateAdapter
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.EditCommand
import androidx.compose.ui.text.input.EditProcessor
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
import kotlinx.coroutines.awaitCancellation
import androidx.compose.ui.text.input.TextFieldValue
import kotlinx.coroutines.flow.MutableSharedFlow

// TODO(https://youtrack.jetbrains.com/issue/COMPOSE-733/Merge-1.6.-Apply-changes-for-the-new-text-input) implement
internal actual suspend fun PlatformTextInputSession.platformSpecificTextInputSession(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked Chinese on this example, it works:

        val text = remember {
            TextFieldState("Hello \uD83E\uDDD1\uD83C\uDFFF\u200D\uD83E\uDDB0")
        }

        BasicTextField(
            state = text,
            modifier = Modifier.onPreviewKeyEvent {
                when {
                    (it.isShiftPressed && it.key == Key.Enter) -> {
                        text.edit {
                            append("Compose")
                        }
                        true
                    }
                    else -> false
                }
            }
        )

state: TransformedTextFieldState,
layoutState: TextLayoutState,
Expand All @@ -34,5 +38,65 @@ internal actual suspend fun PlatformTextInputSession.platformSpecificTextInputSe
stylusHandwritingTrigger: MutableSharedFlow<Unit>?,
viewConfiguration: ViewConfiguration?
): Nothing {
awaitCancellation()
}
val editProcessor = EditProcessor()
fun onEditCommand(commands: List<EditCommand>) {
editProcessor.reset(
value = with(state.visualText) {
TextFieldValue(
text = toString(),
selection = selection,
composition = composition
)
},
textInputSession = null
)

val newValue = editProcessor.apply(commands)

state.replaceAll(newValue.text)
state.editUntransformedTextAsUser {
val untransformedSelection = state.mapFromTransformed(newValue.selection)
setSelection(untransformedSelection.start, untransformedSelection.end)

val composition = newValue.composition
if (composition == null) {
commitComposition()
} else {
val untransformedComposition = state.mapFromTransformed(composition)
setComposition(untransformedComposition.start, untransformedComposition.end)
}
}
}

startInputMethod(
SkikoPlatformTextInputMethodRequest(
state = TransformedTextFieldStateAdapter(state),
imeOptions = imeOptions,
onEditCommand = ::onEditCommand,
onImeAction = onImeAction
)
)
}


private class TransformedTextFieldStateAdapter(
val state: TransformedTextFieldState
) : TextFieldStateAdapter {

override val text: CharSequence
get() = state.visualText

override val selection: TextRange
get() = state.visualText.selection

override val composition: TextRange?
get() = state.visualText.composition

}

private data class SkikoPlatformTextInputMethodRequest(
override val state: TextFieldStateAdapter,
override val imeOptions: ImeOptions,
override val onEditCommand: (List<EditCommand>) -> Unit,
override val onImeAction: ((ImeAction) -> Unit)?
): PlatformTextInputMethodRequest
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.node.RootForTest
import androidx.compose.ui.platform.InfiniteAnimationPolicy
import androidx.compose.ui.platform.PlatformContext
import androidx.compose.ui.platform.PlatformContextTextInputService
import androidx.compose.ui.platform.TextFieldStateAdapter
import androidx.compose.ui.platform.WindowInfo
import androidx.compose.ui.scene.ComposeScene
import androidx.compose.ui.scene.ComposeSceneContext
Expand All @@ -35,8 +37,6 @@ import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.text.input.EditCommand
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
import androidx.compose.ui.text.input.PlatformTextInputService
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import kotlin.coroutines.CoroutineContext
Expand Down Expand Up @@ -391,19 +391,19 @@ class SkikoComposeUiTest @InternalTestApi constructor(
get() = size
}

private inner class TestTextInputService : PlatformTextInputService {
private inner class TestTextInputService : PlatformContextTextInputService {
var session: Session? = null

override fun startInput(
value: TextFieldValue,
value: TextFieldStateAdapter,
imeOptions: ImeOptions,
onEditCommand: (List<EditCommand>) -> Unit,
onImeActionPerformed: (ImeAction) -> Unit
onImeActionPerformed: ((ImeAction) -> Unit)?
) {
session = Session(
imeOptions = imeOptions,
onEditCommand = onEditCommand,
onImeActionPerformed = onImeActionPerformed
onImeActionPerformed = onImeActionPerformed ?: { }
)
}

Expand All @@ -413,13 +413,13 @@ class SkikoComposeUiTest @InternalTestApi constructor(

override fun showSoftwareKeyboard() = Unit
override fun hideSoftwareKeyboard() = Unit
override fun updateState(oldValue: TextFieldValue?, newValue: TextFieldValue) = Unit
override fun updateState(oldValue: TextFieldStateAdapter?, newValue: TextFieldStateAdapter) = Unit
}

private inner class TestContext : PlatformContext by PlatformContext.Empty {
override val windowInfo: WindowInfo = TestWindowInfo()

override val textInputService: PlatformTextInputService = TestTextInputService()
override val textInputService = TestTextInputService()

override val rootForTestListener: PlatformContext.RootForTestListener
get() = composeRootRegistry
Expand Down
42 changes: 37 additions & 5 deletions compose/ui/ui/api/desktop/ui.api
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ public final class androidx/compose/ui/ComposableSingletons$ImageComposeScene_sk

public final class androidx/compose/ui/ComposeScene {
public static final field $stable I
public fun <init> (Landroidx/compose/ui/text/input/PlatformTextInputService;Lkotlin/coroutines/CoroutineContext;Landroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;Lkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Landroidx/compose/ui/text/input/PlatformTextInputService;Lkotlin/coroutines/CoroutineContext;Landroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Landroidx/compose/ui/text/input/PlatformTextInputService;Lkotlin/coroutines/CoroutineContext;Landroidx/compose/ui/unit/Density;Lkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Landroidx/compose/ui/text/input/PlatformTextInputService;Lkotlin/coroutines/CoroutineContext;Landroidx/compose/ui/unit/Density;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Landroidx/compose/ui/platform/PlatformContextTextInputService;Lkotlin/coroutines/CoroutineContext;Landroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;Lkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Landroidx/compose/ui/platform/PlatformContextTextInputService;Lkotlin/coroutines/CoroutineContext;Landroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Landroidx/compose/ui/platform/PlatformContextTextInputService;Lkotlin/coroutines/CoroutineContext;Landroidx/compose/ui/unit/Density;Lkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Landroidx/compose/ui/platform/PlatformContextTextInputService;Lkotlin/coroutines/CoroutineContext;Landroidx/compose/ui/unit/Density;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lkotlin/coroutines/CoroutineContext;Landroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;Lkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Lkotlin/coroutines/CoroutineContext;Landroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lkotlin/coroutines/CoroutineContext;Landroidx/compose/ui/unit/Density;Lkotlin/jvm/functions/Function0;)V
Expand Down Expand Up @@ -3292,13 +3292,15 @@ public abstract interface class androidx/compose/ui/platform/PlatformContext {
public fun getParentFocusManager ()Landroidx/compose/ui/focus/FocusManager;
public fun getRootForTestListener ()Landroidx/compose/ui/platform/PlatformContext$RootForTestListener;
public fun getSemanticsOwnerListener ()Landroidx/compose/ui/platform/PlatformContext$SemanticsOwnerListener;
public fun getTextInputService ()Landroidx/compose/ui/text/input/PlatformTextInputService;
public fun getTextInputService ()Landroidx/compose/ui/platform/PlatformContextTextInputService;
public fun getTextToolbar ()Landroidx/compose/ui/platform/TextToolbar;
public fun getViewConfiguration ()Landroidx/compose/ui/platform/ViewConfiguration;
public abstract fun getWindowInfo ()Landroidx/compose/ui/platform/WindowInfo;
public fun isWindowTransparent ()Z
public fun requestFocus ()Z
public fun setPointerIcon (Landroidx/compose/ui/input/pointer/PointerIcon;)V
public fun textInputSession (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun textInputSession$suspendImpl (Landroidx/compose/ui/platform/PlatformContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class androidx/compose/ui/platform/PlatformContext$Companion {
Expand All @@ -3317,6 +3319,21 @@ public abstract interface class androidx/compose/ui/platform/PlatformContext$Sem
public abstract fun onSemanticsOwnerRemoved (Landroidx/compose/ui/semantics/SemanticsOwner;)V
}

public abstract interface class androidx/compose/ui/platform/PlatformContextTextInputService {
public abstract fun hideSoftwareKeyboard ()V
public fun notifyFocusedRect (Landroidx/compose/ui/geometry/Rect;)V
public abstract fun showSoftwareKeyboard ()V
public fun startInput ()V
public abstract fun startInput (Landroidx/compose/ui/platform/TextFieldStateAdapter;Landroidx/compose/ui/text/input/ImeOptions;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
public abstract fun stopInput ()V
public abstract fun updateState (Landroidx/compose/ui/platform/TextFieldStateAdapter;Landroidx/compose/ui/platform/TextFieldStateAdapter;)V
public fun updateTextLayoutResult (Landroidx/compose/ui/platform/TextFieldStateAdapter;Landroidx/compose/ui/text/input/OffsetMapping;Landroidx/compose/ui/text/TextLayoutResult;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/geometry/Rect;Landroidx/compose/ui/geometry/Rect;)V
}

public final class androidx/compose/ui/platform/PlatformContext_skikoKt {
public static final fun asPlatformTextInputService (Landroidx/compose/ui/platform/PlatformContextTextInputService;)Landroidx/compose/ui/text/input/PlatformTextInputService;
}

public abstract interface class androidx/compose/ui/platform/PlatformDragAndDropManager {
public abstract fun drag-12SF9DM (Landroidx/compose/ui/draganddrop/DragAndDropTransferData;JLkotlin/jvm/functions/Function1;)Z
public abstract fun getModifier ()Landroidx/compose/ui/Modifier;
Expand Down Expand Up @@ -3367,6 +3384,15 @@ public abstract interface class androidx/compose/ui/platform/PlatformTextInputIn
}

public abstract interface class androidx/compose/ui/platform/PlatformTextInputMethodRequest {
public abstract fun getImeOptions ()Landroidx/compose/ui/text/input/ImeOptions;
public abstract fun getOnEditCommand ()Lkotlin/jvm/functions/Function1;
public abstract fun getOnImeAction ()Lkotlin/jvm/functions/Function1;
public abstract fun getState ()Landroidx/compose/ui/platform/TextFieldStateAdapter;
}

public final class androidx/compose/ui/platform/PlatformTextInputMethodRequest_skikoKt {
public static final fun asTextFieldStateAdapter (Landroidx/compose/ui/text/input/TextFieldValue;)Landroidx/compose/ui/platform/TextFieldStateAdapter;
public static final fun toTextFieldValue (Landroidx/compose/ui/platform/TextFieldStateAdapter;)Landroidx/compose/ui/text/input/TextFieldValue;
}

public abstract interface class androidx/compose/ui/platform/PlatformTextInputModifierNode : androidx/compose/ui/node/DelegatableNode {
Expand All @@ -3393,6 +3419,12 @@ public final class androidx/compose/ui/platform/TestTagKt {
public static final fun testTag (Landroidx/compose/ui/Modifier;Ljava/lang/String;)Landroidx/compose/ui/Modifier;
}

public abstract interface class androidx/compose/ui/platform/TextFieldStateAdapter {
public abstract fun getComposition-MzsxiRA ()Landroidx/compose/ui/text/TextRange;
public abstract fun getSelection-d9O1mEE ()J
public abstract fun getText ()Ljava/lang/CharSequence;
}

public abstract interface class androidx/compose/ui/platform/TextToolbar {
public abstract fun getStatus ()Landroidx/compose/ui/platform/TextToolbarStatus;
public abstract fun hide ()V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ import androidx.compose.ui.text.input.DeleteSurroundingTextInCodePointsCommand
import androidx.compose.ui.text.input.EditCommand
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
import androidx.compose.ui.text.input.PlatformTextInputService
import androidx.compose.ui.text.input.SetComposingTextCommand
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.substring
import java.awt.Rectangle
import java.awt.event.InputMethodEvent
Expand All @@ -34,16 +32,16 @@ import java.awt.im.InputMethodRequests
import java.text.AttributedCharacterIterator
import java.text.AttributedString
import java.text.CharacterIterator
import java.util.Locale
import java.util.*
import kotlin.math.max
import kotlin.math.min

internal class DesktopTextInputService(private val component: PlatformComponent) :
PlatformTextInputService {
PlatformContextTextInputService {
data class CurrentInput(
var value: TextFieldValue,
var value: TextFieldStateAdapter,
val onEditCommand: ((List<EditCommand>) -> Unit),
val onImeActionPerformed: ((ImeAction) -> Unit),
val onImeActionPerformed: ((ImeAction) -> Unit)?,
val imeAction: ImeAction,
var focusedRect: Rect? = null
)
Expand All @@ -58,13 +56,16 @@ internal class DesktopTextInputService(private val component: PlatformComponent)
var needToDeletePreviousChar: Boolean = false

override fun startInput(
value: TextFieldValue,
value: TextFieldStateAdapter,
imeOptions: ImeOptions,
onEditCommand: (List<EditCommand>) -> Unit,
onImeActionPerformed: (ImeAction) -> Unit
onImeActionPerformed: ((ImeAction) -> Unit)?
) {
val input = CurrentInput(
value, onEditCommand, onImeActionPerformed, imeOptions.imeAction
value = value,
onEditCommand = onEditCommand,
onImeActionPerformed = onImeActionPerformed,
imeAction = imeOptions.imeAction
)
currentInput = input

Expand All @@ -82,15 +83,13 @@ internal class DesktopTextInputService(private val component: PlatformComponent)
override fun hideSoftwareKeyboard() {
}

override fun updateState(oldValue: TextFieldValue?, newValue: TextFieldValue) {
currentInput?.let { input ->
input.value = newValue
}
override fun updateState(oldValue: TextFieldStateAdapter?, newValue: TextFieldStateAdapter) {
currentInput?.value = newValue
}

// TODO(https://github.com/JetBrains/compose-jb/issues/2040): probably the position of input method
// popup isn't correct now
@Deprecated("This method should not be called, used BringIntoViewRequester instead.")
@Deprecated("This method should not be called, use BringIntoViewRequester instead.")
override fun notifyFocusedRect(rect: Rect) {
currentInput?.let { input ->
input.focusedRect = rect
Expand Down
Loading