Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -21,6 +21,7 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand Down Expand Up @@ -51,6 +52,7 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.offset
import com.sdds.compose.uikit.IndicatorMode
import com.sdds.compose.uikit.LocalTextFieldStyle
Expand Down Expand Up @@ -208,12 +210,32 @@ internal fun BaseTextField(

val verticalScrollState = if (!singleLine) rememberScrollState() else null
val horizontalScrollState = if (singleLine) rememberScrollState() else null

var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
LaunchedEffect(value.text) {
horizontalScrollState?.scrollTo(value = Int.MAX_VALUE)
verticalScrollState?.scrollTo(value = Int.MAX_VALUE)
var innerFieldSize by remember { mutableStateOf(IntSize.Zero) }
var chipGroupSize by remember { mutableStateOf(IntSize.Zero) }
var prefixSize by remember { mutableStateOf(IntSize.Zero) }
val innerTextFieldInfo = remember {
derivedStateOf {
InnerTextFieldLayoutInfo(
fieldSize = innerFieldSize,
prefixSize = prefixSize,
chipGroupSize = chipGroupSize,
)
}
}

LaunchedEffect(value.text, value.selection, innerTextFieldInfo.value) {
textLayoutResult?.let { layout ->
scrollToCaret(
value,
layout,
horizontalScrollState,
verticalScrollState,
innerTextFieldInfo.value,
)
}
}
/**
* Источник взаимодействий внутреннего поля.
* Когда внешний фокус выключен, он совпадает с [interactionSource].
Expand Down Expand Up @@ -386,6 +408,9 @@ internal fun BaseTextField(
text = suffix,
textStyle = suffixStyle,
),
onInnerTextFieldSizeChanged = { fieldSize -> innerFieldSize = fieldSize },
onChipGroupSizeChanged = { chipsSize -> chipGroupSize = chipsSize },
onPrefixSizeChanged = { prefix -> prefixSize = prefix },
)

OuterBottomText(
Expand Down Expand Up @@ -414,6 +439,43 @@ internal fun BaseTextField(
)
}

private suspend fun scrollToCaret(
value: TextFieldValue,
layout: TextLayoutResult,
horizontalScrollState: ScrollState?,
verticalScrollState: ScrollState?,
innerFieldInfo: InnerTextFieldLayoutInfo,
) {
val prefixSize = innerFieldInfo.prefixSize
val cursorRect = layout
.getCursorRect(value.selection.end)
.translate(prefixSize.width.toFloat(), 0f)
horizontalScrollState?.let { scroll ->
val chipsWidth = innerFieldInfo.chipGroupSize.width
val fieldWidth = innerFieldInfo.fieldSize.width
val cursorRight = cursorRect.right.toInt()
val cursorLeft = cursorRect.left.toInt()
val target = when {
chipsWidth + cursorRight > scroll.value + fieldWidth -> chipsWidth + cursorRight - fieldWidth
chipsWidth + cursorLeft < scroll.value -> chipsWidth + cursorLeft
else -> null
}
if (target != null && target != scroll.value) scroll.scrollTo(target)
}
verticalScrollState?.let { scroll ->
val chipsHeight = innerFieldInfo.chipGroupSize.height
val fieldHeight = innerFieldInfo.fieldSize.height
val cursorTop = cursorRect.top.toInt()
val cursorBottom = cursorRect.bottom.toInt()
val target = when {
chipsHeight + cursorBottom > scroll.value + fieldHeight -> chipsHeight + cursorBottom - fieldHeight
chipsHeight + cursorTop < scroll.value -> chipsHeight + cursorTop
else -> null
}
if (target != null && target != scroll.value) scroll.scrollTo(target)
}
}

@Composable
private fun TextFieldColors.indicatorColor(
readOnly: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.IntSize
import com.sdds.compose.uikit.ChipGroupStyle
import com.sdds.compose.uikit.TextFieldAnimation
import com.sdds.compose.uikit.TextFieldDimensions
Expand Down Expand Up @@ -47,6 +48,9 @@ internal fun DecorationBox(
prefix: (@Composable () -> Unit)?,
suffix: (@Composable () -> Unit)?,
textLayoutResult: TextLayoutResult?,
onInnerTextFieldSizeChanged: (IntSize) -> Unit,
onChipGroupSizeChanged: (IntSize) -> Unit,
onPrefixSizeChanged: (IntSize) -> Unit,
) {
val isFocused = interactionSource.collectIsFocusedAsState().value
val inputState = when {
Expand Down Expand Up @@ -94,6 +98,9 @@ internal fun DecorationBox(
prefix = prefix,
suffix = suffix,
textLayoutResult = textLayoutResult,
onInnerTextFieldSizeChanged = onInnerTextFieldSizeChanged,
onChipGroupSizeChanged = onChipGroupSizeChanged,
onPrefixSizeChanged = onPrefixSizeChanged,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.constrainHeight
import androidx.compose.ui.unit.constrainWidth
import androidx.compose.ui.unit.dp
Expand All @@ -32,11 +33,12 @@ internal fun PrefixSuffixWrapper(
prefix: (@Composable () -> Unit)?,
suffix: (@Composable () -> Unit)?,
textLayoutResult: TextLayoutResult? = null,
onPrefixSizeChanged: ((IntSize) -> Unit)? = null,
) {
Layout(
modifier = modifier,
measurePolicy = remember(textLayoutResult?.getSuffixCoordinates()) {
PrefixSuffixMeasurePolicy(textLayoutResult)
PrefixSuffixMeasurePolicy(textLayoutResult, onPrefixSizeChanged)
},
content = {
Box(
Expand Down Expand Up @@ -68,6 +70,7 @@ internal fun PrefixSuffixWrapper(

private class PrefixSuffixMeasurePolicy(
private val textLayoutResult: TextLayoutResult?,
private val onPrefixSizeChanged: ((IntSize) -> Unit)?,
) : MeasurePolicy {

override fun MeasureScope.measure(
Expand All @@ -77,6 +80,12 @@ private class PrefixSuffixMeasurePolicy(
val prefixPlaceable = measurables
.firstOrNull { it.layoutId == PREFIX_ID }
?.measure(constraints.copy(minWidth = 0, minHeight = 0))
onPrefixSizeChanged?.invoke(
IntSize(
prefixPlaceable.widthOrZero(),
prefixPlaceable.heightOrZero(),
),
)
val suffixPlaceable = measurables
.firstOrNull { it.layoutId == SUFFIX_ID }
?.measure(constraints.copy(minWidth = 0, minHeight = 0))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand All @@ -27,6 +28,7 @@ import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi
Expand All @@ -36,12 +38,12 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.constrainHeight
import androidx.compose.ui.unit.constrainWidth
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.offset
import com.sdds.compose.uikit.ChipGroup
import com.sdds.compose.uikit.ChipGroupOverflowMode
import com.sdds.compose.uikit.ChipGroupStyle
import com.sdds.compose.uikit.TextFieldDimensions
import com.sdds.compose.uikit.internal.focusselector.FocusSelectorMode
Expand Down Expand Up @@ -80,6 +82,9 @@ internal fun TextFieldLayout(
prefix: (@Composable () -> Unit)?,
suffix: (@Composable () -> Unit)?,
textLayoutResult: TextLayoutResult?,
onInnerTextFieldSizeChanged: (IntSize) -> Unit,
onChipGroupSizeChanged: (IntSize) -> Unit,
onPrefixSizeChanged: (IntSize) -> Unit,
) {
val hasChips = chips != null
val chipHeight = chipGroupStyle.chipStyle.dimensions.height
Expand All @@ -102,6 +107,7 @@ internal fun TextFieldLayout(
chipHeight = chipHeight,
alignmentLine = alignmentLine,
hasChips = hasChips,
onInnerTextFieldMeasured = onInnerTextFieldSizeChanged,
)
}
Layout(
Expand Down Expand Up @@ -169,12 +175,21 @@ internal fun TextFieldLayout(
prefix = prefix,
suffix = suffix,
textLayoutResult = textLayoutResult,
onChipGroupSizeChanged = onChipGroupSizeChanged,
onPrefixSizeChanged = onPrefixSizeChanged,
)
},
measurePolicy = measurePolicy,
)
}

@Immutable
internal data class InnerTextFieldLayoutInfo(
val fieldSize: IntSize,
val prefixSize: IntSize,
val chipGroupSize: IntSize,
)

private fun adjustStartPaddingWhenHasChips(
hasChips: Boolean,
startPadding: Dp,
Expand Down Expand Up @@ -252,6 +267,8 @@ private fun CompositeTextFieldContent(
prefix: (@Composable () -> Unit)?,
suffix: (@Composable () -> Unit)?,
textLayoutResult: TextLayoutResult?,
onChipGroupSizeChanged: (IntSize) -> Unit,
onPrefixSizeChanged: (IntSize) -> Unit,
) {
val textContent: @Composable () -> Unit = {
Box {
Expand All @@ -263,6 +280,7 @@ private fun CompositeTextFieldContent(
prefix = prefix,
suffix = suffix,
textLayoutResult = textLayoutResult,
onPrefixSizeChanged = onPrefixSizeChanged,
)
}
}
Expand All @@ -279,6 +297,7 @@ private fun CompositeTextFieldContent(
dimensions = dimensions,
scrollState = verticalScrollState,
valueTextStyle = valueTextStyle,
onChipGroupSizeChanged = onChipGroupSizeChanged,
)
} else {
TextFieldContent(
Expand All @@ -288,6 +307,7 @@ private fun CompositeTextFieldContent(
chipGroupStyle = chipGroupStyle,
dimensions = dimensions,
scrollState = horizontalScrollState,
onChipGroupSizeChanged = onChipGroupSizeChanged,
)
}
}
Expand All @@ -309,6 +329,7 @@ private fun TextAreaContent(
dimensions: TextFieldDimensions,
scrollState: ScrollState?,
valueTextStyle: TextStyle,
onChipGroupSizeChanged: (IntSize) -> Unit,
) {
val chipStyle = chipGroupStyle.chipStyle
Column(
Expand All @@ -324,13 +345,26 @@ private fun TextAreaContent(
val chipHeight = chipStyle.dimensions.height
val chipSpacing = chipGroupStyle.dimensions.lineSpacing
val chipsBottomPadding = chipSpacing + (chipHeight - valueHeight) / 2
val chipsBottomPaddingPx = with(LocalDensity.current) {
chipsBottomPadding.roundToPx()
}
ChipGroup(
modifier = Modifier.padding(bottom = chipsBottomPadding),
overflowMode = ChipGroupOverflowMode.Wrap,
modifier = Modifier
.onSizeChanged {
onChipGroupSizeChanged.invoke(
IntSize(
width = it.width,
height = if (it.height != 0) it.height + chipsBottomPaddingPx else 0,
),
)
}
.padding(bottom = chipsBottomPadding),
style = chipGroupStyle,
) {
chips.invoke()
}
} else {
onChipGroupSizeChanged.invoke(IntSize.Zero)
}
Box(
modifier = Modifier.padding(
Expand All @@ -355,6 +389,7 @@ private fun TextFieldContent(
chipGroupStyle: ChipGroupStyle,
dimensions: TextFieldDimensions,
scrollState: ScrollState?,
onChipGroupSizeChanged: (IntSize) -> Unit,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
Expand All @@ -367,14 +402,26 @@ private fun TextFieldContent(
.then(scrollState?.let { Modifier.horizontalScroll(it) } ?: Modifier),
content = {
if (chips != null) {
val gapPx = with(LocalDensity.current) {
chipGroupStyle.dimensions.gap.roundToPx()
}
ChipGroup(
modifier = Modifier
.onSizeChanged {
onChipGroupSizeChanged.invoke(
IntSize(
width = if (it.width != 0) it.width + gapPx else 0,
height = it.height,
),
)
}
.padding(end = dimensions.boxPaddingStart + chipGroupStyle.dimensions.gap),
style = chipGroupStyle,
overflowMode = ChipGroupOverflowMode.Unlimited,
) {
chips.invoke()
}
} else {
onChipGroupSizeChanged.invoke(IntSize.Zero)
}
textContent()
},
Expand Down Expand Up @@ -402,6 +449,7 @@ constructor(
private val chipHeight: Dp,
private val alignmentLine: Dp,
private val hasChips: Boolean,
private val onInnerTextFieldMeasured: (IntSize) -> Unit,
) : MeasurePolicy {

@OptIn(ExperimentalTextApi::class)
Expand Down Expand Up @@ -456,6 +504,9 @@ constructor(
val textFieldPlaceable = measurables
.first { it.layoutId == TextFieldId }
.measure(textFieldConstraints)
onInnerTextFieldMeasured.invoke(
IntSize(textFieldPlaceable.width, textFieldPlaceable.height),
)

// measure placeholder
val placeholderConstraints = textFieldConstraints.copy(minWidth = 0)
Expand Down