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 11 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,17 @@
package androidx.compose.foundation.text.input.internal

import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.PlatformTextInputMethodRequest
import androidx.compose.ui.platform.PlatformTextInputSession
import androidx.compose.ui.platform.ViewConfiguration
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 +37,54 @@ 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 = TextFieldValue(
state.visualText.toString(),
state.visualText.selection,
state.visualText.composition,
),
imeOptions = imeOptions,
onEditCommand = ::onEditCommand,
onImeAction = onImeAction
)
)
}

@OptIn(ExperimentalComposeUiApi::class)
private data class SkikoPlatformTextInputMethodRequest(
override val state: TextFieldValue,
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 @@ -419,7 +419,7 @@ class SkikoComposeUiTest @InternalTestApi constructor(
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import androidx.compose.ui.text.TextLayoutResult
* close it with [stopInput].
*/
// Open for testing purposes.
@Deprecated("Use PlatformTextInputModifierNode instead.")
open class TextInputService(private val platformTextInputService: PlatformTextInputService) {
private val _currentInputSession: AtomicReference<TextInputSession?> =
AtomicReference(null)
Expand Down Expand Up @@ -294,7 +293,6 @@ class TextInputSession(
/**
* Platform specific text input service.
*/
@Deprecated("Use PlatformTextInputModifierNode instead.")
interface PlatformTextInputService {
/**
* Start text input session for given client.
Expand Down
6 changes: 6 additions & 0 deletions compose/ui/ui/api/desktop/ui.api
Original file line number Diff line number Diff line change
Expand Up @@ -3299,6 +3299,8 @@ public abstract interface class androidx/compose/ui/platform/PlatformContext {
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 Down Expand Up @@ -3367,6 +3369,10 @@ 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/text/input/TextFieldValue;
}

public abstract interface class androidx/compose/ui/platform/PlatformTextInputModifierNode : androidx/compose/ui/node/DelegatableNode {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ internal class DesktopTextInputService(private val component: PlatformComponent)
data class CurrentInput(
var value: TextFieldValue,
val onEditCommand: ((List<EditCommand>) -> Unit),
val onImeActionPerformed: ((ImeAction) -> Unit),
val onImeActionPerformed: (ImeAction) -> Unit,
val imeAction: ImeAction,
var focusedRect: Rect? = null
)
Expand All @@ -64,7 +64,10 @@ internal class DesktopTextInputService(private val component: PlatformComponent)
onImeActionPerformed: (ImeAction) -> Unit
) {
val input = CurrentInput(
value, onEditCommand, onImeActionPerformed, imeOptions.imeAction
value = value,
onEditCommand = onEditCommand,
onImeActionPerformed = onImeActionPerformed,
imeAction = imeOptions.imeAction
)
currentInput = input

Expand All @@ -83,14 +86,12 @@ internal class DesktopTextInputService(private val component: PlatformComponent)
}

override fun updateState(oldValue: TextFieldValue?, newValue: TextFieldValue) {
currentInput?.let { input ->
input.value = newValue
}
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import androidx.compose.ui.input.key.KeyEvent as ComposeKeyEvent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalContext
import androidx.compose.ui.ComposeFeatureFlags
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.SessionMutex
import androidx.compose.ui.awt.AwtEventListener
import androidx.compose.ui.awt.AwtEventListeners
import androidx.compose.ui.awt.OnlyValidPrimaryMouseButtonFilter
Expand All @@ -46,14 +48,15 @@ import androidx.compose.ui.platform.DesktopTextInputService
import androidx.compose.ui.platform.EmptyViewConfiguration
import androidx.compose.ui.platform.PlatformComponent
import androidx.compose.ui.platform.PlatformContext
import androidx.compose.ui.platform.PlatformTextInputMethodRequest
import androidx.compose.ui.platform.PlatformTextInputSessionScope
import androidx.compose.ui.platform.PlatformWindowContext
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.platform.WindowInfo
import androidx.compose.ui.platform.a11y.AccessibilityController
import androidx.compose.ui.platform.a11y.ComposeSceneAccessible
import androidx.compose.ui.scene.skia.SkiaLayerComponent
import androidx.compose.ui.semantics.SemanticsOwner
import androidx.compose.ui.text.input.PlatformTextInputService
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
Expand Down Expand Up @@ -87,6 +90,8 @@ import javax.swing.JComponent
import javax.swing.SwingUtilities
import kotlin.coroutines.CoroutineContext
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.suspendCancellableCoroutine
import org.jetbrains.skia.Canvas
import org.jetbrains.skiko.ClipRectangle
import org.jetbrains.skiko.ExperimentalSkikoApi
Expand Down Expand Up @@ -666,7 +671,18 @@ internal class ComposeSceneMediator(

override val measureDrawLayerBounds: Boolean = this@ComposeSceneMediator.measureDrawLayerBounds
override val viewConfiguration: ViewConfiguration = DesktopViewConfiguration()
override val textInputService: PlatformTextInputService = this@ComposeSceneMediator.textInputService
override val textInputService = this@ComposeSceneMediator.textInputService

private val textInputSessionMutex = SessionMutex<DesktopTextInputSession>()

override suspend fun textInputSession(
session: suspend PlatformTextInputSessionScope.() -> Nothing
): Nothing = textInputSessionMutex.withSessionCancellingPrevious(
sessionInitializer = {
DesktopTextInputSession(coroutineScope = it)
},
session = session
)

override fun setPointerIcon(pointerIcon: PointerIcon) {
contentComponent.cursor =
Expand Down Expand Up @@ -718,6 +734,35 @@ internal class ComposeSceneMediator(
get() = contentComponent.density
}

@OptIn(InternalComposeUiApi::class)
private inner class DesktopTextInputSession(
coroutineScope: CoroutineScope,
) : PlatformTextInputSessionScope, CoroutineScope by coroutineScope {

private val innerSessionMutex = SessionMutex<Nothing?>()

override suspend fun startInputMethod(
request: PlatformTextInputMethodRequest
): Nothing = innerSessionMutex.withSessionCancellingPrevious(
// This session has no data, just init/dispose tasks.
sessionInitializer = { null }
) {
(suspendCancellableCoroutine<Nothing> { continuation ->
textInputService.startInput(
value = request.state,
imeOptions = request.imeOptions,
onEditCommand = request.onEditCommand,
onImeActionPerformed = request.onImeAction ?: {}
)

continuation.invokeOnCancellation {
textInputService.stopInput()
}
})
}
}


private class InvisibleComponent : Component() {
fun requestFocusTemporary(): Boolean {
return super.requestFocus(true)
Expand Down
Loading