From 0a3cb60599702e1bc164ca48f8b55568bec89358 Mon Sep 17 00:00:00 2001 From: davide magli Date: Tue, 3 Oct 2023 09:28:50 +0200 Subject: [PATCH] Add leading icon to TextField and LabelledTextField components --- .../org/jetbrains/jewel/LabelledTextField.kt | 4 +++ .../kotlin/org/jetbrains/jewel/TextField.kt | 32 +++++++++++++++++-- .../jewel/samples/standalone/Main.kt | 2 +- .../standalone/components/TextFields.kt | 20 +++++++++++- 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/jewel/LabelledTextField.kt b/core/src/main/kotlin/org/jetbrains/jewel/LabelledTextField.kt index 05a1937fd..b29bf87ac 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/LabelledTextField.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/LabelledTextField.kt @@ -41,6 +41,7 @@ fun LabelledTextField( hint: @Composable (() -> Unit)? = null, placeholder: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, undecorated: Boolean = false, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, @@ -72,6 +73,7 @@ fun LabelledTextField( hint = hint, placeholder = placeholder, trailingIcon = trailingIcon, + leadingIcon = leadingIcon, undecorated = undecorated, visualTransformation = visualTransformation, keyboardOptions = keyboardOptions, @@ -103,6 +105,7 @@ fun LabelledTextField( hint: @Composable (() -> Unit)? = null, placeholder: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, undecorated: Boolean = false, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, @@ -131,6 +134,7 @@ fun LabelledTextField( outline = outline, placeholder = placeholder, trailingIcon = trailingIcon, + leadingIcon = leadingIcon, undecorated = undecorated, visualTransformation = visualTransformation, keyboardOptions = keyboardOptions, diff --git a/core/src/main/kotlin/org/jetbrains/jewel/TextField.kt b/core/src/main/kotlin/org/jetbrains/jewel/TextField.kt index f499a8f0e..7d414143e 100644 --- a/core/src/main/kotlin/org/jetbrains/jewel/TextField.kt +++ b/core/src/main/kotlin/org/jetbrains/jewel/TextField.kt @@ -41,6 +41,7 @@ fun TextField( outline: Outline = Outline.None, placeholder: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, undecorated: Boolean = false, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, @@ -71,6 +72,7 @@ fun TextField( outline = outline, placeholder = placeholder, trailingIcon = trailingIcon, + leadingIcon = leadingIcon, undecorated = undecorated, visualTransformation = visualTransformation, keyboardOptions = keyboardOptions, @@ -94,6 +96,7 @@ fun TextField( readOnly: Boolean = false, outline: Outline = Outline.None, placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, undecorated: Boolean = false, visualTransformation: VisualTransformation = VisualTransformation.None, @@ -132,6 +135,7 @@ fun TextField( placeholderTextColor = style.colors.placeholder, placeholder = if (value.text.isEmpty()) placeholder else null, trailingIcon = trailingIcon, + leadingIcon = leadingIcon, ) } } @@ -144,10 +148,16 @@ private fun TextFieldDecorationBox( placeholderTextColor: Color, placeholder: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, ) { Layout( modifier = modifier, content = { + if (leadingIcon != null) { + Box(modifier = Modifier.layoutId(LEADING_ID), contentAlignment = Alignment.Center) { + leadingIcon() + } + } if (trailingIcon != null) { Box(modifier = Modifier.layoutId(TRAILING_ID), contentAlignment = Alignment.Center) { trailingIcon() @@ -175,7 +185,11 @@ private fun TextFieldDecorationBox( // measure trailing icon val trailingPlaceable = measurables.find { it.layoutId == TRAILING_ID } ?.measure(iconConstraints) + + val leadingPlaceable = measurables.find { it.layoutId == LEADING_ID } + ?.measure(iconConstraints) occupiedSpaceHorizontally += trailingPlaceable?.width ?: 0 + occupiedSpaceHorizontally += leadingPlaceable?.width ?: 0 val textFieldConstraints = incomingConstraints.offset( horizontal = -occupiedSpaceHorizontally, @@ -188,12 +202,14 @@ private fun TextFieldDecorationBox( val width = calculateWidth( trailingPlaceable, + leadingPlaceable, textFieldPlaceable, placeholderPlaceable, incomingConstraints, ) val height = calculateHeight( trailingPlaceable, + leadingPlaceable, textFieldPlaceable, placeholderPlaceable, incomingConstraints, @@ -204,6 +220,7 @@ private fun TextFieldDecorationBox( height, width, trailingPlaceable, + leadingPlaceable, textFieldPlaceable, placeholderPlaceable, ) @@ -213,6 +230,7 @@ private fun TextFieldDecorationBox( private fun calculateWidth( trailingPlaceable: Placeable?, + leadingPlaceable: Placeable?, textFieldPlaceable: Placeable, placeholderPlaceable: Placeable?, constraints: Constraints, @@ -221,12 +239,13 @@ private fun calculateWidth( textFieldPlaceable.width, placeholderPlaceable?.width ?: 0, ) - val wrappedWidth = middleSection + (trailingPlaceable?.width ?: 0) + val wrappedWidth = middleSection + (trailingPlaceable?.width ?: 0) + (leadingPlaceable?.width ?: 0) return max(wrappedWidth, constraints.minWidth) } private fun calculateHeight( trailingPlaceable: Placeable?, + leadingPlaceable: Placeable?, textFieldPlaceable: Placeable, placeholderPlaceable: Placeable?, constraints: Constraints, @@ -234,6 +253,7 @@ private fun calculateHeight( textFieldPlaceable.height, placeholderPlaceable?.height ?: 0, trailingPlaceable?.height ?: 0, + leadingPlaceable?.height ?: 0, constraints.minHeight, ) @@ -241,6 +261,7 @@ private fun Placeable.PlacementScope.place( height: Int, width: Int, trailingPlaceable: Placeable?, + leadingPlaceable: Placeable?, textFieldPlaceable: Placeable, placeholderPlaceable: Placeable?, ) { @@ -249,16 +270,20 @@ private fun Placeable.PlacementScope.place( width - trailingPlaceable.width, Alignment.CenterVertically.align(trailingPlaceable.height, height), ) + leadingPlaceable?.placeRelative( + 0, + Alignment.CenterVertically.align(leadingPlaceable.height, height), + ) // placed center vertically textFieldPlaceable.placeRelative( - 0, + leadingPlaceable?.width ?: 0, Alignment.CenterVertically.align(textFieldPlaceable.height, height), ) // placed similar to the input text above placeholderPlaceable?.placeRelative( - 0, + leadingPlaceable?.width ?: 0, Alignment.CenterVertically.align(placeholderPlaceable.height, height), ) } @@ -266,3 +291,4 @@ private fun Placeable.PlacementScope.place( private const val PLACEHOLDER_ID = "Placeholder" private const val TEXT_FIELD_ID = "TextField" private const val TRAILING_ID = "Trailing" +private const val LEADING_ID = "Leading" diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt index 1b5f3fcf1..478560363 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/Main.kt @@ -103,7 +103,7 @@ private fun ComponentShowcase(svgLoader: JewelSvgLoader, resourceLoader: Resourc Checkboxes() RadioButtons() Links() - TextFields() + TextFields(svgLoader, resourceLoader) TextAreas() ProgressBar(svgLoader) ChipsAndTree() diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/TextFields.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/TextFields.kt index 5fcf04f32..0fbe47a73 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/TextFields.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/components/TextFields.kt @@ -2,21 +2,27 @@ package org.jetbrains.jewel.samples.standalone.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.ResourceLoader import androidx.compose.ui.unit.dp import org.jetbrains.jewel.GroupHeader +import org.jetbrains.jewel.Icon import org.jetbrains.jewel.LabelledTextField import org.jetbrains.jewel.Outline +import org.jetbrains.jewel.SvgLoader import org.jetbrains.jewel.Text import org.jetbrains.jewel.TextField +import org.jetbrains.jewel.styling.ResourcePainterProvider @Composable -fun TextFields() { +fun TextFields(svgLoader: SvgLoader, resourceLoader: ResourceLoader) { GroupHeader("TextFields") Row( horizontalArrangement = Arrangement.spacedBy(10.dp), @@ -59,4 +65,16 @@ fun TextFields() { placeholder = { Text("Labelled TextField with hint") }, ) } + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.Top, + ) { + var text by remember { mutableStateOf("With leading icon") } + TextField(text, { text = it }, enabled = true, leadingIcon = { + val searchIcon by remember { ResourcePainterProvider.stateless("icons/search.svg", svgLoader) }.getPainter( + resourceLoader, + ) + Icon(searchIcon, "SearchIcon", Modifier.size(16.dp)) + }) + } }