Skip to content

Commit c0251cd

Browse files
committed
Squashed the following changes:
- Fixed lack of items in the context menu with NITI - Forwarded cursor / selection colors in TF1 / TF2 - Disabled Compose cursor in NITI mode in both TFs - Created expect / actual for the Compose text selection highlight in TF1, hidden it under NITI flag in uikit sourceset - Renamed UIKitTextContextMenuHandler.kt into more appropriate UIKitNativeTextInputContext - Created expect / actual for altering drawing selection rect on different platforms - fixed incorrect positioning of context menu - disabled back newContextMenu flag, rewrote showEditMenu logic in UIKitTextInputService.uikit.kt with using NITI toggle, created a signle entry point for context menu, removed some comments - context menu without NITI fixes + added existing obj-c files to the project which were forgotten to add during the merge - merge fixes after commit with refactoring InputViews.uikit.kt - Reverted pointerInput handling in TF1,2, reverted view hierarchy, hidden almost everything related to the NITI under the flag, added some TODOs with bad fixes - Added feature flag for NITI to platform ime options - added NITI tests screens to the example app - Forwarded custom actions to the IntermediateTextInputUIView.uikit.kt - Enabled native context menu, added public API for updating context menu state (foundation -> ui), extracted all UITextInput methods from objc file to separate class, temporarily disabled current context menu - Extracted UITextInput methods from CMPEditMenuView to CMPTextInputView, fixed NewContextMenuApi menu - (wip) commented CMPEditMenu methods calls after rebase with new context menu api on ios - (wip) turned on native selection rects (LTR only!) + fixed appearing of the text editing menu by tapping on the selection rects + more appropriate naming + fixed imports - (wip) disabled custom iOS tap handlers in TF1,2 - (wip) fixed native text views positioning in TF1 - (wip) disabled compose selection handles - (wip) fixed scroll positioning in BTF2 - (wip) fixed touch forwarding - (wip) fixed positioning of ScrollView and TextView issue - (wip) removed unnecessary comments - (wip) fixed sizing of TextScrollView and TextView - (wip) transfered changes from previous niti branch - (wip) rebase fixes
1 parent 612833a commit c0251cd

File tree

53 files changed

+2747
-169
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2747
-169
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.compose.foundation.text
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.ui.graphics.Brush
21+
import androidx.compose.ui.graphics.Color
22+
23+
@Composable
24+
internal actual fun platformShouldDrawTextControls(cursorBrush: Brush, selectionColor: Color): Boolean = false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.compose.foundation.text.input.internal
18+
19+
import androidx.compose.ui.geometry.Rect
20+
import androidx.compose.ui.graphics.Brush
21+
import androidx.compose.ui.graphics.drawscope.DrawScope
22+
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
23+
import androidx.compose.ui.text.TextLayoutResult
24+
import androidx.compose.ui.text.TextRange
25+
26+
internal actual fun CompositionLocalConsumerModifierNode.drawPlatformSelection(
27+
scope: DrawScope,
28+
selection: TextRange,
29+
textLayoutResult: TextLayoutResult
30+
) = drawDefaultSelection(scope, selection, textLayoutResult)
31+
32+
internal actual fun CompositionLocalConsumerModifierNode.drawPlatformCursor(
33+
scope: DrawScope,
34+
cursorRect: Rect,
35+
brush: Brush,
36+
alpha: Float
37+
) = drawDefaultCursor(scope, cursorRect, brush, alpha)

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ComposeFoundationFlags.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ object ComposeFoundationFlags {
6868
* [BasicTextField][androidx.compose.foundation.text.BasicTextField]s. If false, the previous
6969
* context menu that has no public APIs will be used instead.
7070
*/
71+
// TODO mazunin-v: don't forget to revert it
7172
@Suppress("MutableBareField") @JvmField var isNewContextMenuEnabled = false
7273

7374
/**

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ internal fun CoreTextField(
387387
manager, enabled, interactionSource, state, focusRequester, readOnly, offsetMapping
388388
)
389389

390+
val platformDrawsTextControls = platformShouldDrawTextControls(cursorBrush, state.selectionBackgroundColor)
390391
val drawModifier =
391392
Modifier.drawBehind {
392393
state.layoutResult?.let { layoutResult ->
@@ -400,6 +401,7 @@ internal fun CoreTextField(
400401
layoutResult.value,
401402
state.highlightPaint,
402403
state.selectionBackgroundColor,
404+
!platformDrawsTextControls
403405
)
404406
}
405407
}
@@ -456,7 +458,7 @@ internal fun CoreTextField(
456458
focusRequester,
457459
)
458460

459-
val showCursor = enabled && !readOnly && windowInfo.isWindowFocused && !state.hasHighlight()
461+
val showCursor = enabled && !readOnly && windowInfo.isWindowFocused && !state.hasHighlight() && !platformDrawsTextControls
460462
val cursorModifier = Modifier.cursor(state, value, offsetMapping, cursorBrush, showCursor)
461463

462464
DisposableEffect(manager) { onDispose { manager.hideSelectionToolbar() } }
@@ -657,7 +659,7 @@ internal fun CoreTextField(
657659
if (
658660
state.handleState == HandleState.Cursor && !readOnly && showHandleAndMagnifier
659661
) {
660-
TextFieldCursorHandle(manager = manager)
662+
// TextFieldCursorHandle(manager = manager)
661663
}
662664
}
663665
}
@@ -1108,6 +1110,16 @@ internal expect fun CursorHandle(
11081110
minTouchTargetSize: DpSize = DpSize.Unspecified,
11091111
)
11101112

1113+
/**
1114+
* Determines whether the platform should handle drawing text controls, such as cursor and selection highlights.
1115+
*
1116+
* @param cursorBrush A brush used to draw the cursor in the text field.
1117+
* @param selectionColor The color used to highlight the selected text.
1118+
* @return A boolean value indicating whether the platform should handle drawing text controls.
1119+
*/
1120+
@Composable
1121+
internal expect fun platformShouldDrawTextControls(cursorBrush: Brush, selectionColor: Color): Boolean
1122+
11111123
// TODO(b/262648050) Try to find a better API.
11121124
private fun notifyFocusedRect(
11131125
state: LegacyTextFieldState,

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ internal class TextFieldDelegate {
135135
textLayoutResult: TextLayoutResult,
136136
highlightPaint: Paint,
137137
selectionBackgroundColor: Color,
138+
drawSelectionHighlight: Boolean = true,
138139
) {
139140
if (!selectionPreviewHighlightRange.collapsed) {
140141
highlightPaint.color = selectionBackgroundColor
@@ -157,7 +158,7 @@ internal class TextFieldDelegate {
157158
textLayoutResult,
158159
highlightPaint,
159160
)
160-
} else if (!value.selection.collapsed) {
161+
} else if (!value.selection.collapsed && drawSelectionHighlight) {
161162
highlightPaint.color = selectionBackgroundColor
162163
drawHighlight(
163164
canvas,

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldCoreModifier.kt

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -518,9 +518,7 @@ internal class TextFieldCoreModifierNode(
518518
val start = selection.min
519519
val end = selection.max
520520
if (start != end) {
521-
val selectionBackgroundColor = currentValueOf(LocalTextSelectionColors).backgroundColor
522-
val selectionPath = textLayoutResult.getPathForRange(start, end)
523-
drawPath(selectionPath, color = selectionBackgroundColor)
521+
this@TextFieldCoreModifierNode.drawPlatformSelection(this, selection, textLayoutResult)
524522
}
525523
}
526524

@@ -571,12 +569,13 @@ internal class TextFieldCoreModifierNode(
571569

572570
val cursorRect = textFieldSelectionState.getCursorRect()
573571

574-
drawLine(
575-
cursorBrush,
576-
cursorRect.topCenter,
577-
cursorRect.bottomCenter,
572+
// Delegate the actual drawing to platform-specific implementation, passing only
573+
// prepared parameters to avoid exposing private members outside this node.
574+
this@TextFieldCoreModifierNode.drawPlatformCursor(
575+
scope = this,
576+
cursorRect = cursorRect,
577+
brush = cursorBrush,
578578
alpha = cursorAlphaValue,
579-
strokeWidth = cursorRect.width,
580579
)
581580
}
582581

@@ -681,3 +680,58 @@ private fun Float.roundToNext(): Float =
681680
this > 0 -> ceil(this)
682681
else -> floor(this)
683682
}
683+
684+
/**
685+
* Draws the visual highlight for the given text [selection].
686+
*
687+
* Platforms may override this to customize how text selection is rendered. The shared default
688+
* implementation is provided by `drawDefaultSelection`.
689+
*
690+
* @param scope [DrawScope] used for issuing drawing commands.
691+
* @param selection Range of selected text in [textLayoutResult].
692+
* @param textLayoutResult Layout information used to map [selection] to canvas coordinates.
693+
*/
694+
internal expect fun CompositionLocalConsumerModifierNode.drawPlatformSelection(scope: DrawScope, selection: TextRange, textLayoutResult: TextLayoutResult)
695+
696+
internal fun CompositionLocalConsumerModifierNode.drawDefaultSelection(scope: DrawScope, selection: TextRange, textLayoutResult: TextLayoutResult) {
697+
val selectionBackgroundColor = currentValueOf(LocalTextSelectionColors).backgroundColor
698+
val selectionPath = textLayoutResult.getPathForRange(selection.min, selection.max)
699+
with(scope) {
700+
drawPath(selectionPath, color = selectionBackgroundColor)
701+
}
702+
}
703+
704+
/**
705+
* Draws the visual cursor indicator using the provided [cursorRect].
706+
*
707+
* Platforms may override this to customize how the text cursor is rendered. The shared default
708+
* implementation is provided by `drawDefaultCursor`.
709+
*
710+
* @param scope [DrawScope] used for issuing drawing commands.
711+
* @param cursorRect Rectangle representing the cursor in canvas coordinates.
712+
* @param brush [Brush] used to paint the cursor.
713+
* @param alpha Opacity to use when drawing the cursor.
714+
*/
715+
internal expect fun CompositionLocalConsumerModifierNode.drawPlatformCursor(
716+
scope: DrawScope,
717+
cursorRect: Rect,
718+
brush: Brush,
719+
alpha: Float,
720+
)
721+
722+
internal fun CompositionLocalConsumerModifierNode.drawDefaultCursor(
723+
scope: DrawScope,
724+
cursorRect: Rect,
725+
brush: Brush,
726+
alpha: Float,
727+
) {
728+
with(scope) {
729+
drawLine(
730+
brush = brush,
731+
start = cursorRect.topCenter,
732+
end = cursorRect.bottomCenter,
733+
alpha = alpha,
734+
strokeWidth = cursorRect.width,
735+
)
736+
}
737+
}

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionState.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ internal class TextFieldSelectionState(
536536
try {
537537
coroutineScope {
538538
launch { observeTextChanges() }
539+
launch { observeSelectionChanges() }
539540
launch { observeTextToolbarVisibility() }
540541
}
541542
} finally {
@@ -546,6 +547,23 @@ internal class TextFieldSelectionState(
546547
}
547548
}
548549

550+
private suspend fun observeSelectionChanges() {
551+
snapshotFlow {
552+
val isCollapsed = textFieldState.visualText.selection.collapsed
553+
if (draggingHandle == null && isInTouchMode) {
554+
if (isCollapsed) {
555+
Cursor
556+
} else {
557+
Selection
558+
}
559+
} else {
560+
None
561+
}
562+
}.collect { state ->
563+
updateTextToolbarState(state)
564+
}
565+
}
566+
549567
fun updateTextToolbarState(textToolbarState: TextToolbarState) {
550568
this.textToolbarState = textToolbarState
551569
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.compose.foundation.text
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.ui.graphics.Brush
21+
import androidx.compose.ui.graphics.Color
22+
23+
@Composable
24+
internal actual fun platformShouldDrawTextControls(cursorBrush: Brush, selectionColor: Color): Boolean = false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.compose.foundation.text.input.internal
18+
19+
import androidx.compose.ui.geometry.Rect
20+
import androidx.compose.ui.graphics.Brush
21+
import androidx.compose.ui.graphics.drawscope.DrawScope
22+
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
23+
import androidx.compose.ui.text.TextLayoutResult
24+
import androidx.compose.ui.text.TextRange
25+
26+
internal actual fun CompositionLocalConsumerModifierNode.drawPlatformSelection(
27+
scope: DrawScope,
28+
selection: TextRange,
29+
textLayoutResult: TextLayoutResult
30+
) = drawDefaultSelection(scope, selection, textLayoutResult)
31+
32+
internal actual fun CompositionLocalConsumerModifierNode.drawPlatformCursor(
33+
scope: DrawScope,
34+
cursorRect: Rect,
35+
brush: Brush,
36+
alpha: Float
37+
) = drawDefaultCursor(scope, cursorRect, brush, alpha)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.compose.foundation.text.selection
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.text.style.ResolvedTextDirection
22+
import androidx.compose.ui.unit.DpSize
23+
24+
@Composable
25+
internal actual fun SelectionHandle(
26+
offsetProvider: OffsetProvider,
27+
isStartHandle: Boolean,
28+
direction: ResolvedTextDirection,
29+
handlesCrossed: Boolean,
30+
minTouchTargetSize: DpSize,
31+
lineHeight: Float,
32+
modifier: Modifier
33+
) {
34+
// TODO: check that JVM target shouldn't require selection handles
35+
}

0 commit comments

Comments
 (0)