Skip to content

Commit

Permalink
Fix initial cursor position in empty TextField when TextAlignment is …
Browse files Browse the repository at this point in the history
…set explicitly (#1354)

After focusing on the textfield and before entering any text, now
blinking cursor will be positioned in accordance with TextAlignment.

Fixes https://youtrack.jetbrains.com/issue/COMPOSE-1360/
Fixes JetBrains/compose-multiplatform#2711
Fixes JetBrains/compose-multiplatform#3098
Fixes JetBrains/compose-multiplatform#4611

## Testing
Manual testing.

## Release Notes
<!--
Optional, if omitted - won't be included in the changelog

Sections:
- Highlights
- Known issues
- Breaking changes
- Features
- Fixes

Subsections:
- Multiple Platforms
- iOS
- Desktop
- Web
- Resources
- Gradle Plugin
-->
### Fixes - Multiple Platforms
- Fix initial cursor position in the empty `TextField` with explicitly
set `TextAlignment`.

## Google CLA
You need to sign the Google Contributor’s License Agreement at
https://cla.developers.google.com/.
This is needed since we synchronise most of the code with Google’s AOSP
repository. Signing this agreement allows us to synchronise code from
your Pull Requests as well.

---------

Co-authored-by: Ivan Matkov <ivan.matkov@jetbrains.com>
  • Loading branch information
mazunin-v-jb and MatkovIvan committed Jun 11, 2024
1 parent 8f37649 commit 6c2eca1
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import androidx.compose.ui.text.platform.SkiaParagraphIntrinsics
import androidx.compose.ui.text.platform.cursorHorizontalPosition
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.text.style.ResolvedTextDirection
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.isUnspecified
Expand Down Expand Up @@ -256,7 +257,7 @@ internal class SkiaParagraph(
val isRtl = paragraphIntrinsics.textDirection == ResolvedTextDirection.Rtl
val isLtr = !isRtl
return when {
prevBox == null && nextBox == null -> if (isRtl) width else 0f
prevBox == null && nextBox == null -> getAlignedStartingPosition(isRtl)
prevBox == null -> nextBox!!.cursorHorizontalPosition(true)
nextBox == null -> prevBox.cursorHorizontalPosition()
nextBox.direction == prevBox.direction -> nextBox.cursorHorizontalPosition(true)
Expand All @@ -269,6 +270,16 @@ internal class SkiaParagraph(
}
}

private fun getAlignedStartingPosition(isRtl: Boolean): Float =
when (layouter.textStyle.textAlign) {
TextAlign.Left -> 0f
TextAlign.Right -> width
TextAlign.Center -> width / 2
TextAlign.Start -> if (isRtl) width else 0f
TextAlign.End -> if (isRtl) 0f else width
else -> 0f
}

private var _lineMetrics: Array<LineMetrics>? = null
private val lineMetrics: Array<LineMetrics>
get() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package androidx.compose.ui.text

import androidx.compose.ui.text.font.createFontFamilyResolver
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import kotlin.test.Ignore
Expand All @@ -28,6 +30,7 @@ import kotlin.test.assertFailsWith
class SkikoParagraphTest {
private val fontFamilyResolver = createFontFamilyResolver()
private val defaultDensity = Density(density = 1f)
private val maxWidthConstraint = 1000

@Test
fun getWordBoundary_out_of_boundary_too_small() {
Expand Down Expand Up @@ -315,7 +318,8 @@ class SkikoParagraphTest {
fun getWordBoundary_multichar() {
// "ab 𐐔𐐯𐑅𐐨𐑉𐐯𐐻 cd" - example of multi-char code units
// | (offset=3) | (offset=6)
val text = "ab \uD801\uDC14\uD801\uDC2F\uD801\uDC45\uD801\uDC28\uD801\uDC49\uD801\uDC2F\uD801\uDC3B cd"
val text =
"ab \uD801\uDC14\uD801\uDC2F\uD801\uDC45\uD801\uDC28\uD801\uDC49\uD801\uDC2F\uD801\uDC3B cd"
val paragraph = simpleParagraph(text)

assertEquals(
Expand All @@ -324,10 +328,60 @@ class SkikoParagraphTest {
)
}

private fun simpleParagraph(text: String) = Paragraph(
@Test
fun getHorizontalPosition_cursor_empty_textfield_ltr_start_alignment() {
val paragraph = simpleParagraph("", TextStyle(textAlign = TextAlign.Start, textDirection = TextDirection.Ltr))
val cursorHorizontalPosition: Float = paragraph.getHorizontalPosition(0, false)
assertEquals(0f, cursorHorizontalPosition)
}

@Test
fun getHorizontalPosition_cursor_empty_textfield_ltr_end_alignment() {
val paragraph = simpleParagraph("", TextStyle(textAlign = TextAlign.End, textDirection = TextDirection.Ltr))
val cursorHorizontalPosition = paragraph.getHorizontalPosition(0, false)
assertEquals(maxWidthConstraint.toFloat(), cursorHorizontalPosition)
}

@Test
fun getHorizontalPosition_cursor_empty_textfield_center_alignment() {
val paragraph = simpleParagraph("", TextStyle(textAlign = TextAlign.Center))
val cursorHorizontalPosition = paragraph.getHorizontalPosition(0, false)
assertEquals((maxWidthConstraint / 2).toFloat(), cursorHorizontalPosition)
}

@Test
fun getHorizontalPosition_cursor_empty_textfield_rtl_start_alignment() {
val paragraph = simpleParagraph("", TextStyle(textAlign = TextAlign.Start, textDirection = TextDirection.Rtl))
val cursorHorizontalPosition: Float = paragraph.getHorizontalPosition(0, false)
assertEquals(maxWidthConstraint.toFloat(), cursorHorizontalPosition)
}

@Test
fun getHorizontalPosition_cursor_empty_textfield_rtl_end_alignment() {
val paragraph = simpleParagraph("", TextStyle(textAlign = TextAlign.End, textDirection = TextDirection.Rtl))
val cursorHorizontalPosition = paragraph.getHorizontalPosition(0, false)
assertEquals(0f, cursorHorizontalPosition)
}

@Test
fun getHorizontalPosition_cursor_empty_textfield_left_alignment() {
val paragraph = simpleParagraph("", TextStyle(textAlign = TextAlign.Start))
val cursorHorizontalPosition: Float = paragraph.getHorizontalPosition(0, false)
assertEquals(0f, cursorHorizontalPosition)
}

@Test
fun getHorizontalPosition_cursor_empty_textfield_right_alignment() {
val paragraph = simpleParagraph("", TextStyle(textAlign = TextAlign.End))
val cursorHorizontalPosition = paragraph.getHorizontalPosition(0, false)
assertEquals(maxWidthConstraint.toFloat(), cursorHorizontalPosition)
}


private fun simpleParagraph(text: String, textStyle: TextStyle = TextStyle()) = Paragraph(
text = text,
style = TextStyle(),
constraints = Constraints(maxWidth = 1000),
style = textStyle,
constraints = Constraints(maxWidth = maxWidthConstraint),
density = defaultDensity,
fontFamilyResolver = fontFamilyResolver
)
Expand Down

0 comments on commit 6c2eca1

Please sign in to comment.