Skip to content

Commit

Permalink
Fix TextField parameter ordering and sizing shenanigans (#285)
Browse files Browse the repository at this point in the history
* Fix TextField parameters order

* Fix TextField layout: ignore placeholders

Also use IconButton in TextField sample
  • Loading branch information
rock3r authored Jan 15, 2024
1 parent 36df5e5 commit 6131b37
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ private fun RowScope.ColumnOne() {
TextField(
value = textFieldValue,
onValueChange = { textFieldValue = it },
placeholder = { Text("Write something...") },
modifier = Modifier.width(200.dp),
placeholder = { Text("Write something...") },
)

var checked by remember { mutableStateOf(false) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,36 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.HoverInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.graphics.Color
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.intui.standalone.styling.dark
import org.jetbrains.jewel.intui.standalone.styling.defaults
import org.jetbrains.jewel.intui.standalone.styling.light
import org.jetbrains.jewel.samples.standalone.StandaloneSampleIcons
import org.jetbrains.jewel.samples.standalone.viewmodel.View
import org.jetbrains.jewel.ui.Outline
import org.jetbrains.jewel.ui.component.Icon
import org.jetbrains.jewel.ui.component.IconButton
import org.jetbrains.jewel.ui.component.Text
import org.jetbrains.jewel.ui.component.TextField
import org.jetbrains.jewel.ui.component.styling.IconButtonColors
import org.jetbrains.jewel.ui.component.styling.IconButtonMetrics
import org.jetbrains.jewel.ui.component.styling.IconButtonStyle
import org.jetbrains.jewel.ui.painter.hints.Stateful
import org.jetbrains.jewel.ui.painter.rememberResourcePainterProvider

@Composable
Expand Down Expand Up @@ -60,9 +66,11 @@ fun TextFields() {
) {
var text1 by remember { mutableStateOf("") }
TextField(
text1,
{ text1 = it },
enabled = true,
value = text1,
onValueChange = { text1 = it },
placeholder = {
Text("With leading icon")
},
leadingIcon = {
Icon(
resource = "icons/search.svg",
Expand All @@ -71,62 +79,75 @@ fun TextFields() {
modifier = Modifier.size(16.dp),
)
},
placeholder = {
Text("With leading icon")
},
)

var text2 by remember { mutableStateOf("") }
TextField(
text2,
{ text2 = it },
enabled = true,
trailingIcon = {
AnimatedVisibility(
visible = text2.isNotEmpty(),
enter = fadeIn() + slideInHorizontally { it / 2 },
exit = fadeOut() + slideOutHorizontally { it / 2 },
) {
CloseIconButton { text2 = "" }
}
},
value = text2,
onValueChange = { text2 = it },
placeholder = {
Text("With trailing button")
},
trailingIcon = {
CloseIconButton(text2.isNotEmpty()) { text2 = "" }
},
)
}
}

@Composable
private fun CloseIconButton(onClick: () -> Unit) {
val interactionSource = remember { MutableInteractionSource() }
var hovered by remember { mutableStateOf(false) }
private fun CloseIconButton(
isVisible: Boolean,
onClick: () -> Unit,
) {
Box(Modifier.size(16.dp)) {
AnimatedVisibility(
visible = isVisible,
enter = fadeIn() + slideInHorizontally { it / 2 },
exit = fadeOut() + slideOutHorizontally { it / 2 },
) {
// TODO replace when IconButton supports no-background style
val isDark = JewelTheme.isDark

LaunchedEffect(interactionSource) {
interactionSource.interactions.collect {
when (it) {
is HoverInteraction.Enter -> hovered = true
is HoverInteraction.Exit -> hovered = false
val colors = noBackgroundIconButtonColors(isDark)
val style = remember(isDark, colors) {
IconButtonStyle(colors, IconButtonMetrics.defaults())
}
}
}

val closeIconProvider = rememberResourcePainterProvider("icons/close.svg", StandaloneSampleIcons::class.java)
val closeIcon by closeIconProvider.getPainter()
IconButton(
onClick,
style = style,
modifier = Modifier.pointerHoverIcon(PointerIcon.Default),
) { state ->
val painterProvider =
rememberResourcePainterProvider("icons/close.svg", StandaloneSampleIcons::class.java)
val painter by painterProvider.getPainter(Stateful(state))

val hoveredCloseIconProvider =
rememberResourcePainterProvider("icons/closeHovered.svg", StandaloneSampleIcons::class.java)
val hoveredCloseIcon by hoveredCloseIconProvider.getPainter()
Icon(painter, contentDescription = "Clear")
}
}
}
}

Icon(
painter = if (hovered) hoveredCloseIcon else closeIcon,
contentDescription = "Clear",
modifier = Modifier
.pointerHoverIcon(PointerIcon.Default)
.clickable(
interactionSource = interactionSource,
indication = null,
role = Role.Button,
) { onClick() },
@Composable
private fun noBackgroundIconButtonColors(isDark: Boolean) = if (isDark) {
IconButtonColors.dark(
background = Color.Unspecified,
backgroundDisabled = Color.Unspecified,
backgroundSelected = Color.Unspecified,
backgroundSelectedActivated = Color.Unspecified,
backgroundFocused = Color.Unspecified,
backgroundPressed = Color.Unspecified,
backgroundHovered = Color.Unspecified,
)
} else {
IconButtonColors.light(
background = Color.Unspecified,
backgroundDisabled = Color.Unspecified,
backgroundSelected = Color.Unspecified,
backgroundSelectedActivated = Color.Unspecified,
backgroundFocused = Color.Unspecified,
backgroundPressed = Color.Unspecified,
backgroundHovered = Color.Unspecified,
)
}
43 changes: 22 additions & 21 deletions ui/src/main/kotlin/org/jetbrains/jewel/ui/component/TextField.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ public fun TextField(
readOnly: Boolean = false,
outline: Outline = Outline.None,
placeholder: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
undecorated: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
Expand Down Expand Up @@ -139,8 +139,8 @@ public fun TextField(
textStyle = textStyle,
placeholderTextColor = style.colors.placeholder,
placeholder = if (value.text.isEmpty()) placeholder else null,
trailingIcon = trailingIcon,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
)
}
}
Expand All @@ -152,8 +152,8 @@ private fun TextFieldDecorationBox(
textStyle: TextStyle,
placeholderTextColor: Color,
placeholder: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
) {
Layout(
modifier = modifier,
Expand Down Expand Up @@ -206,72 +206,73 @@ private fun TextFieldDecorationBox(
?.measure(placeholderConstraints)

val width = calculateWidth(
trailingPlaceable,
leadingPlaceable,
trailingPlaceable,
textFieldPlaceable,
placeholderPlaceable,
incomingConstraints,
)
val height = calculateHeight(
trailingPlaceable,
leadingPlaceable,
trailingPlaceable,
textFieldPlaceable,
placeholderPlaceable,
incomingConstraints,
)

layout(width, height) {
place(height, width, trailingPlaceable, leadingPlaceable, textFieldPlaceable, placeholderPlaceable)
place(
height = height,
width = width,
leadingPlaceable = leadingPlaceable,
trailingPlaceable = trailingPlaceable,
textFieldPlaceable = textFieldPlaceable,
placeholderPlaceable = placeholderPlaceable,
)
}
}
}

private fun calculateWidth(
trailingPlaceable: Placeable?,
leadingPlaceable: Placeable?,
trailingPlaceable: Placeable?,
textFieldPlaceable: Placeable,
placeholderPlaceable: Placeable?,
constraints: Constraints,
): Int {
val middleSection =
maxOf(textFieldPlaceable.width, placeholderPlaceable?.width ?: 0)
val middleSection = textFieldPlaceable.width
val wrappedWidth =
middleSection + (trailingPlaceable?.width ?: 0) + (leadingPlaceable?.width ?: 0)
return max(wrappedWidth, constraints.minWidth)
}

private fun calculateHeight(
trailingPlaceable: Placeable?,
leadingPlaceable: Placeable?,
trailingPlaceable: Placeable?,
textFieldPlaceable: Placeable,
placeholderPlaceable: Placeable?,
constraints: Constraints,
): Int =
maxOf(
textFieldPlaceable.height,
placeholderPlaceable?.height ?: 0,
trailingPlaceable?.height ?: 0,
leadingPlaceable?.height ?: 0,
trailingPlaceable?.height ?: 0,
constraints.minHeight,
)

private fun Placeable.PlacementScope.place(
height: Int,
width: Int,
trailingPlaceable: Placeable?,
leadingPlaceable: Placeable?,
trailingPlaceable: Placeable?,
textFieldPlaceable: Placeable,
placeholderPlaceable: Placeable?,
) {
// placed center vertically and to the end edge horizontally
trailingPlaceable?.placeRelative(
width - trailingPlaceable.width,
Alignment.CenterVertically.align(trailingPlaceable.height, height),
)
leadingPlaceable?.placeRelative(
0,
Alignment.CenterVertically.align(leadingPlaceable.height, height),
)
trailingPlaceable?.placeRelative(
width - trailingPlaceable.width,
Alignment.CenterVertically.align(trailingPlaceable.height, height),
)

// placed center vertically
textFieldPlaceable.placeRelative(
Expand Down

0 comments on commit 6131b37

Please sign in to comment.