Skip to content

Commit

Permalink
Implement TextFieldScrollState and TextFieldScrollbarAdapter (#384)
Browse files Browse the repository at this point in the history
  • Loading branch information
m-sasha authored Jan 26, 2023
1 parent c7220b0 commit 125f049
Show file tree
Hide file tree
Showing 7 changed files with 769 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ internal fun CoreTextField(
enabled: Boolean = true,
readOnly: Boolean = false,
decorationBox: @Composable (innerTextField: @Composable () -> Unit) -> Unit =
@Composable { innerTextField -> innerTextField() }
@Composable { innerTextField -> innerTextField() },
textScrollerPosition: TextFieldScrollerPosition? = null,
) {
val focusRequester = FocusRequester()

Expand All @@ -205,10 +206,20 @@ internal fun CoreTextField(
// Scroll state
val singleLine = maxLines == 1 && !softWrap && imeOptions.singleLine
val orientation = if (singleLine) Orientation.Horizontal else Orientation.Vertical
val scrollerPosition = rememberSaveable(
val scrollerPosition = textScrollerPosition ?: rememberSaveable(
orientation,
saver = TextFieldScrollerPosition.Saver
) { TextFieldScrollerPosition(orientation) }
if (scrollerPosition.orientation != orientation){
throw IllegalArgumentException(
"Mismatching scroller orientation; " + (
if (orientation == Orientation.Vertical)
"only single-line, non-wrap text fields can scroll horizontally"
else
"single-line, non-wrap text fields can only scroll horizontally"
)
)
}

// State
val transformedText = remember(value, visualTransformation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ internal class TextFieldScrollerPosition(
var maximum by mutableStateOf(0f)
private set

/**
* Size of the visible part, on the scrollable axis, in pixels.
*/
var viewportSize by mutableStateOf(0)
private set

/**
* Keeps the cursor position before a new symbol has been typed or the text field has been
* dragged. We check it to understand if the [offset] needs to be updated.
Expand Down Expand Up @@ -279,6 +285,7 @@ internal class TextFieldScrollerPosition(
previousCursorRect = cursorRect
}
offset = offset.coerceIn(0f, difference)
viewportSize = containerSize
}

/*@VisibleForTesting*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.TextFieldScrollState
import androidx.compose.foundation.v2.LazyGridScrollbarAdapter
import androidx.compose.foundation.v2.LazyListScrollbarAdapter
import androidx.compose.foundation.v2.ScrollableScrollbarAdapter
import androidx.compose.foundation.v2.SliderAdapter
import androidx.compose.foundation.v2.TextFieldScrollbarAdapter
import androidx.compose.foundation.v2.maxScrollOffset
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocal
Expand Down Expand Up @@ -569,8 +571,6 @@ fun OldScrollbarAdapter(
*
* [scrollState] is instance of [LazyListState] which is used by scrollable component
*
* Scrollbar size and position will be dynamically changed on the current visible content.
*
* Example:
* Box(Modifier.fillMaxSize()) {
* val state = rememberLazyListState()
Expand Down Expand Up @@ -634,6 +634,19 @@ fun rememberScrollbarAdapter(
ScrollbarAdapter(scrollState)
}

/**
* Create and [remember] [androidx.compose.foundation.v2.ScrollbarAdapter] for text field with
* the given instance of [TextFieldScrollState].
*/
@ExperimentalFoundationApi
@JvmName("rememberScrollbarAdapter2")
@Composable
fun rememberScrollbarAdapter(
scrollState: TextFieldScrollState,
): androidx.compose.foundation.v2.ScrollbarAdapter = remember(scrollState) {
ScrollbarAdapter(scrollState)
}

/**
* ScrollbarAdapter for Modifier.verticalScroll and Modifier.horizontalScroll
*
Expand Down Expand Up @@ -663,8 +676,6 @@ fun ScrollbarAdapter(
*
* [scrollState] is instance of [LazyListState] which is used by scrollable component
*
* Scrollbar size and position will be dynamically changed on the current visible content.
*
* Example:
* Box(Modifier.fillMaxSize()) {
* val state = rememberLazyListState()
Expand All @@ -689,8 +700,6 @@ fun ScrollbarAdapter(
*
* [scrollState] is instance of [LazyGridState] which is used by scrollable component
*
* Scrollbar size and position will be dynamically changed on the current visible content.
*
* Example:
* Box(Modifier.fillMaxSize()) {
* val state = rememberLazyGridState()
Expand All @@ -710,6 +719,35 @@ fun ScrollbarAdapter(
scrollState: LazyGridState
): androidx.compose.foundation.v2.ScrollbarAdapter = LazyGridScrollbarAdapter(scrollState)

/**
* ScrollbarAdapter for text fields.
*
* [scrollState] is instance of [TextFieldScrollState] which is used by scrollable component
*
* Example:
* Box(Modifier.fillMaxSize()) {
* val scrollState = rememberTextFieldVerticalScrollState()
*
* BasicTextField(
* value = ...,
* onValueChange = ...,
* scrollState = state
* ) {
* ...
* }
*
* VerticalScrollbar(
* adapter = rememberScrollbarAdapter(scrollState)
* modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(),
* )
* }
*/
@ExperimentalFoundationApi
@JvmName("ScrollbarAdapter2")
fun ScrollbarAdapter(
scrollState: TextFieldScrollState
): androidx.compose.foundation.v2.ScrollbarAdapter = TextFieldScrollbarAdapter(scrollState)

/**
* Defines how to scroll the scrollable component
*/
Expand Down
Loading

0 comments on commit 125f049

Please sign in to comment.