feat(scripting): text input component

- add visibility attribute
This commit is contained in:
rhunk
2024-02-04 16:01:30 +01:00
parent 283f0266d9
commit 381b346c7a
5 changed files with 157 additions and 76 deletions

View File

@ -12,6 +12,7 @@ import me.rhunk.snapenhance.common.scripting.ui.components.NodeType
import me.rhunk.snapenhance.common.scripting.ui.components.impl.ActionNode
import me.rhunk.snapenhance.common.scripting.ui.components.impl.ActionType
import me.rhunk.snapenhance.common.scripting.ui.components.impl.RowColumnNode
import me.rhunk.snapenhance.common.scripting.ui.components.impl.TextInputNode
import me.rhunk.snapenhance.common.ui.createComposeAlertDialog
import org.mozilla.javascript.Function
import org.mozilla.javascript.annotations.JSFunction
@ -74,6 +75,12 @@ class InterfaceBuilder {
attributes["items"] = items
attributes["callback"] = callback
}
fun textInput(placeholder: String, value: String, callback: (String) -> Unit) = TextInputNode().apply {
placeholder(placeholder)
value(value)
callback(callback)
}.also { nodes.add(it) }
}

View File

@ -1,10 +1,12 @@
package me.rhunk.snapenhance.common.scripting.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.*
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Slider
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -68,92 +70,117 @@ private fun DrawNode(node: Node) {
)
}
when (node.type) {
NodeType.ACTION -> {
when ((node as ActionNode).actionType) {
ActionType.LAUNCHED -> {
LaunchedEffect(node.key) {
runCallbackSafe {
node.callback()
if (cachedAttributes["visibility"] != "gone") {
AnimatedVisibility(
visible = cachedAttributes["visibility"] != "invisible",
) {
when (node.type) {
NodeType.ACTION -> {
when ((node as ActionNode).actionType) {
ActionType.LAUNCHED -> {
LaunchedEffect(node.key) {
runCallbackSafe {
node.callback()
}
}
}
}
}
ActionType.DISPOSE -> {
DisposableEffect(Unit) {
onDispose {
runCallbackSafe {
node.callback()
ActionType.DISPOSE -> {
DisposableEffect(Unit) {
onDispose {
runCallbackSafe {
node.callback()
}
}
}
}
}
}
}
}
NodeType.COLUMN -> {
Column(
verticalArrangement = arrangement as? Arrangement.Vertical ?: spacing?.let { Arrangement.spacedBy(it.dp) } ?: Arrangement.Top,
horizontalAlignment = alignment as? Alignment.Horizontal ?: Alignment.Start,
modifier = rowColumnModifier
) {
node.children.forEach { child ->
DrawNode(child)
}
}
}
NodeType.ROW -> {
Row(
horizontalArrangement = arrangement as? Arrangement.Horizontal ?: spacing?.let { Arrangement.spacedBy(it.dp) } ?: Arrangement.SpaceBetween,
verticalAlignment = alignment as? Alignment.Vertical ?: Alignment.CenterVertically,
modifier = rowColumnModifier
) {
node.children.forEach { child ->
DrawNode(child)
}
}
}
NodeType.TEXT -> NodeLabel()
NodeType.SWITCH -> {
var switchState by remember {
mutableStateOf(cachedAttributes["state"] as Boolean)
}
Switch(
checked = switchState,
onCheckedChange = { state ->
runCallbackSafe {
switchState = state
node.setAttribute("state", state)
(cachedAttributes["callback"] as? (Boolean) -> Unit)?.let { it(state) }
NodeType.COLUMN -> {
Column(
verticalArrangement = arrangement as? Arrangement.Vertical ?: spacing?.let { Arrangement.spacedBy(it.dp) } ?: Arrangement.Top,
horizontalAlignment = alignment as? Alignment.Horizontal ?: Alignment.Start,
modifier = rowColumnModifier
) {
node.children.forEach { child ->
DrawNode(child)
}
}
}
)
}
NodeType.SLIDER -> {
var sliderValue by remember {
mutableFloatStateOf((cachedAttributes["value"] as Int).toFloat())
}
Slider(
value = sliderValue,
onValueChange = { value ->
runCallbackSafe {
sliderValue = value
node.setAttribute("value", value.toInt())
(cachedAttributes["callback"] as? (Int) -> Unit)?.let { it(value.toInt()) }
NodeType.ROW -> {
Row(
horizontalArrangement = arrangement as? Arrangement.Horizontal ?: spacing?.let { Arrangement.spacedBy(it.dp) } ?: Arrangement.SpaceBetween,
verticalAlignment = alignment as? Alignment.Vertical ?: Alignment.CenterVertically,
modifier = rowColumnModifier
) {
node.children.forEach { child ->
DrawNode(child)
}
}
},
valueRange = (cachedAttributes["min"] as Int).toFloat()..(cachedAttributes["max"] as Int).toFloat(),
steps = cachedAttributes["step"] as Int,
)
}
NodeType.BUTTON -> {
OutlinedButton(onClick = {
runCallbackSafe {
(cachedAttributes["callback"] as? () -> Unit)?.let { it() }
}
}) {
NodeLabel()
NodeType.TEXT -> NodeLabel()
NodeType.SWITCH -> {
var switchState by remember {
mutableStateOf(cachedAttributes["state"] as Boolean)
}
Switch(
checked = switchState,
onCheckedChange = { state ->
runCallbackSafe {
switchState = state
node.setAttribute("state", state)
(cachedAttributes["callback"] as? (Boolean) -> Unit)?.let { it(state) }
}
}
)
}
NodeType.SLIDER -> {
var sliderValue by remember {
mutableFloatStateOf((cachedAttributes["value"] as Int).toFloat())
}
Slider(
value = sliderValue,
onValueChange = { value ->
runCallbackSafe {
sliderValue = value
node.setAttribute("value", value.toInt())
(cachedAttributes["callback"] as? (Int) -> Unit)?.let { it(value.toInt()) }
}
},
valueRange = (cachedAttributes["min"] as Int).toFloat()..(cachedAttributes["max"] as Int).toFloat(),
steps = cachedAttributes["step"] as Int,
)
}
NodeType.BUTTON -> {
OutlinedButton(onClick = {
runCallbackSafe {
(cachedAttributes["callback"] as? () -> Unit)?.let { it() }
}
}) {
NodeLabel()
}
}
NodeType.TEXT_INPUT -> {
var textInputValue by remember {
mutableStateOf(cachedAttributes["value"].toString())
}
TextField(
value = textInputValue,
readOnly = cachedAttributes["readonly"] as? Boolean ?: false,
singleLine = cachedAttributes["singleLine"] as? Boolean ?: true,
maxLines = cachedAttributes["maxLines"] as? Int ?: 1,
onValueChange = { value ->
runCallbackSafe {
textInputValue = value
node.setAttribute("value", value)
(cachedAttributes["callback"] as? (String) -> Unit)?.let { it(value) }
}
},
placeholder = { Text(cachedAttributes["placeholder"].toString()) }
)
}
else -> {}
}
}
else -> {}
}
}

View File

@ -1,5 +1,6 @@
package me.rhunk.snapenhance.common.scripting.ui.components
@Suppress("MemberVisibilityCanBePrivate")
open class Node(
val type: NodeType,
) {
@ -16,6 +17,10 @@ open class Node(
}
}
init {
visibility("visible")
}
fun setAttribute(key: String, value: Any?) {
attributes[key] = value
}
@ -49,4 +54,9 @@ open class Node(
attributes["color"] = color
return this
}
fun visibility(state: String) {
assert(state == "visible" || state == "invisible" || state == "gone") { "Invalid visibility state. Must be one of: visible, invisible, gone" }
attributes["visibility"] = state
}
}

View File

@ -8,5 +8,6 @@ enum class NodeType {
BUTTON,
SLIDER,
LIST,
ACTION
ACTION,
TEXT_INPUT,
}

View File

@ -0,0 +1,36 @@
package me.rhunk.snapenhance.common.scripting.ui.components.impl
import me.rhunk.snapenhance.common.scripting.ui.components.Node
import me.rhunk.snapenhance.common.scripting.ui.components.NodeType
class TextInputNode : Node(NodeType.TEXT_INPUT) {
fun placeholder(text: String): TextInputNode {
attributes["placeholder"] = text
return this
}
fun value(text: String): TextInputNode {
attributes["value"] = text
return this
}
fun callback(callback: (String) -> Unit): TextInputNode {
attributes["callback"] = callback
return this
}
fun readonly(state: Boolean): TextInputNode {
attributes["readonly"] = state
return this
}
fun singleLine(state: Boolean): TextInputNode {
attributes["singleLine"] = state
return this
}
fun maxLines(maxLines: Int): TextInputNode {
attributes["maxLines"] = maxLines
return this
}
}