Skip to content

Commit

Permalink
Add TextArea (#39)
Browse files Browse the repository at this point in the history
* Add TextArea

* Update TextAreaStyle.kt

---------

Co-authored-by: Fabrizio Scarponi <36624359+fscarponi@users.noreply.github.com>
  • Loading branch information
RivanParmar and fscarponi committed May 2, 2023
1 parent 7a535fe commit a74cff1
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 0 deletions.
3 changes: 3 additions & 0 deletions core/src/main/kotlin/org/jetbrains/jewel/IntelliJTheme.kt
Expand Up @@ -16,6 +16,7 @@ import org.jetbrains.jewel.styles.LocalRadioButtonStyle
import org.jetbrains.jewel.styles.LocalSeparatorStyle
import org.jetbrains.jewel.styles.LocalSliderStyle
import org.jetbrains.jewel.styles.LocalTabStyle
import org.jetbrains.jewel.styles.LocalTextAreaStyle
import org.jetbrains.jewel.styles.LocalTextFieldStyle
import org.jetbrains.jewel.styles.LocalTextStyle
import org.jetbrains.jewel.styles.LocalTreeViewStyle
Expand All @@ -24,6 +25,7 @@ import org.jetbrains.jewel.styles.ScrollbarStyle
import org.jetbrains.jewel.styles.SeparatorStyle
import org.jetbrains.jewel.styles.SliderStyle
import org.jetbrains.jewel.styles.TabStyle
import org.jetbrains.jewel.styles.TextAreaStyle
import org.jetbrains.jewel.styles.TextFieldStyle
import org.jetbrains.jewel.styles.TreeViewStyle
import org.jetbrains.jewel.styles.localNotProvided
Expand All @@ -46,6 +48,7 @@ fun IntelliJTheme(
LocalButtonStyle provides ButtonStyle(palette, metrics, typography.button),
LocalIconButtonStyle provides ButtonStyle(palette, metrics, typography.button),
LocalCheckboxStyle provides CheckboxStyle(palette, painters, typography.checkBox),
LocalTextAreaStyle provides TextAreaStyle(palette, metrics, typography.textField),
LocalTextFieldStyle provides TextFieldStyle(palette, metrics, typography.textField),
LocalRadioButtonStyle provides RadioButtonStyle(palette, painters, typography.radioButton),
LocalSeparatorStyle provides SeparatorStyle(palette, metrics),
Expand Down
87 changes: 87 additions & 0 deletions core/src/main/kotlin/org/jetbrains/jewel/components/TextArea.kt
@@ -0,0 +1,87 @@
package org.jetbrains.jewel.components

import androidx.compose.foundation.focusable
import androidx.compose.foundation.interaction.FocusInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.BasicTextField
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.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import kotlinx.coroutines.flow.onEach
import org.jetbrains.jewel.components.state.TextAreaState
import org.jetbrains.jewel.shape
import org.jetbrains.jewel.styles.LocalTextAreaStyle
import org.jetbrains.jewel.styles.LocalTextStyle
import org.jetbrains.jewel.styles.TextAreaStyle

@Composable
fun TextArea(
modifier: Modifier = Modifier,
value: String,
onValueChange: (String) -> Unit,
enabled: Boolean = true,
maxLines: Int,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
style: TextAreaStyle = LocalTextAreaStyle.current,
variation: Any? = null
) {
var inputState by remember(interactionSource, enabled) { mutableStateOf(TextAreaState(enabled)) }

LaunchedEffect(interactionSource) {
interactionSource.interactions.onEach { interaction ->
when (interaction) {
is FocusInteraction.Focus -> inputState = inputState.copy(focused = true)
is FocusInteraction.Unfocus -> inputState = inputState.copy(focused = false)
}
}
}

val appearance = style.appearance(inputState, variation)

val shapeModifier = if (appearance.shapeStroke != null || appearance.backgroundColor != Color.Unspecified) {
Modifier.shape(appearance.shape, appearance.shapeStroke, appearance.backgroundColor)
} else {
Modifier
}

val haloStroke = appearance.haloStroke
val haloModifier = when {
haloStroke != null -> Modifier.drawBehind {
drawRect(
brush = haloStroke.brush,
style = Stroke(haloStroke.width.toPx())
)
}
else -> Modifier
}

BasicTextField(
modifier = modifier
.size(width = appearance.width, height = appearance.height)
.focusable(enabled, interactionSource)
.then(shapeModifier)
.then(haloModifier),
value = value,
onValueChange = { onValueChange(it) },
readOnly = false,
singleLine = false,
maxLines = maxLines,
textStyle = LocalTextStyle.current.merge(appearance.textStyle),
cursorBrush = appearance.cursorBrush,
decorationBox = { coreTextField ->
Box(Modifier.padding(appearance.contentPadding)) {
coreTextField()
}
}
)
}
@@ -0,0 +1,7 @@
package org.jetbrains.jewel.components.state

data class TextAreaState(
val enabled: Boolean = true,
val focused: Boolean = false,
val hovered: Boolean = false
)
156 changes: 156 additions & 0 deletions core/src/main/kotlin/org/jetbrains/jewel/styles/TextAreaStyle.kt
@@ -0,0 +1,156 @@
package org.jetbrains.jewel.styles

import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.jetbrains.jewel.Insets
import org.jetbrains.jewel.IntelliJMetrics
import org.jetbrains.jewel.IntelliJPalette
import org.jetbrains.jewel.ShapeStroke
import org.jetbrains.jewel.components.state.TextAreaState
import org.jetbrains.jewel.toBrush

typealias TextAreaStyle = ControlStyle<TextAreaAppearance, TextAreaState>

data class TextAreaAppearance(
val textStyle: TextStyle = TextStyle.Default,
val backgroundColor: Color,
val shapeStroke: ShapeStroke<*>? = null,
val shape: Shape,

val cursorBrush: Brush = SolidColor(Color.Black),
val contentPadding: PaddingValues,

val haloStroke: ShapeStroke<*>? = null,

val height: Dp = Dp.Unspecified,
val width: Dp = Dp.Unspecified
)

val LocalTextAreaStyle = compositionLocalOf<TextAreaStyle> { localNotProvided() }
val Styles.textArea: TextAreaStyle
@Composable
@ReadOnlyComposable
get() = LocalTextAreaStyle.current

fun TextAreaStyle(
palette: IntelliJPalette,
metrics: IntelliJMetrics,
textStyle: TextStyle
) = TextAreaStyle {
val defaultAppearance = TextAreaAppearance(
textStyle = textStyle.copy(
color = palette.textField.foreground,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
),
backgroundColor = palette.textField.background,
shape = RectangleShape,
contentPadding = PaddingValues(7.dp, 4.dp),
cursorBrush = palette.text.toBrush(),
shapeStroke = ShapeStroke.SolidColor(1.dp, palette.controlStroke, Insets(0.dp)),
width = 270.dp,
height = 55.dp
)

val disabledAppearance = defaultAppearance.copy(
textStyle = defaultAppearance.textStyle.copy(color = palette.textField.foregroundDisabled),
backgroundColor = palette.textField.backgroundDisabled
)

val focusedAppearance = defaultAppearance.copy(
shapeStroke = ShapeStroke.SolidColor(1.dp, palette.controlStrokeFocused, Insets(0.dp))
)

default {
allStateCombinations { enabled, focused, hovered ->
val appearance = when {
enabled -> when {
focused -> focusedAppearance
else -> defaultAppearance
}
else -> disabledAppearance
}

state(
TextAreaState(
enabled = enabled,
focused = focused,
hovered = hovered
),
appearance
)
}
}

variation(IntellijTextAreaVariations.Error) {
allStateCombinations { enabled, focused, hovered ->
val appearance = if (enabled) {
defaultAppearance.copy(
shapeStroke = ShapeStroke.SolidColor(1.dp, palette.controlHaloError, Insets(1.dp)),
haloStroke = ShapeStroke.SolidColor(metrics.controlFocusHaloWidth, palette.controlInactiveHaloError, Insets((-1).dp))
)
} else {
disabledAppearance
}

state(
TextAreaState(
enabled = enabled,
focused = focused,
hovered = hovered
),
appearance
)
}
}

variation(IntellijTextAreaVariations.Warning) {
allStateCombinations { enabled, focused, hovered ->
val appearance = when {
enabled -> defaultAppearance.copy(
shapeStroke = ShapeStroke.SolidColor(1.dp, palette.controlHaloWarning, Insets(1.dp)),
haloStroke = ShapeStroke.SolidColor(metrics.controlFocusHaloWidth, palette.controlInactiveHaloWarning, Insets((-1).dp))
)

else -> disabledAppearance
}

state(
TextAreaState(
enabled = enabled,
focused = focused,
hovered = hovered
),
appearance
)
}
}
}

private fun ControlStyle.ControlVariationBuilder<TextAreaAppearance, TextAreaState>.allStateCombinations(
action: ControlStyle.ControlVariationBuilder<TextAreaAppearance, TextAreaState>.(enabled: Boolean, focused: Boolean, hovered: Boolean) -> Unit
) {
for (enabled in listOf(false, true)) {
for (focused in listOf(false, true)) {
for (hovered in listOf(false, true)) {
action(enabled, focused, hovered)
}
}
}
}

enum class IntellijTextAreaVariations {
Error,
Warning
}

0 comments on commit a74cff1

Please sign in to comment.