Basic character sheet edit.

This commit is contained in:
Thomas Andres Gomez 2024-11-03 16:02:29 +01:00
parent 6e4f91e007
commit d74a5fcd7c
3 changed files with 501 additions and 2 deletions

View file

@ -1,20 +1,31 @@
package com.pixelized.desktop.lwa package com.pixelized.desktop.lwa
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Surface import androidx.compose.material.Surface
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window import androidx.compose.ui.window.Window
import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheet import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheet
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetUio
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetViewModel import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetViewModel
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEdit
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEditUio
import com.pixelized.desktop.lwa.screen.characterSheet.edit.FieldUio
import com.pixelized.desktop.lwa.screen.overlay.BlurOverlay import com.pixelized.desktop.lwa.screen.overlay.BlurOverlay
import com.pixelized.desktop.lwa.screen.overlay.BlurOverlayViewModel import com.pixelized.desktop.lwa.screen.overlay.BlurOverlayViewModel
import com.pixelized.desktop.lwa.screen.roll.RollPage import com.pixelized.desktop.lwa.screen.roll.RollPage
@ -33,13 +44,34 @@ fun App() {
val overlayViewModel = viewModel { BlurOverlayViewModel() } val overlayViewModel = viewModel { BlurOverlayViewModel() }
val rollViewModel = viewModel { RollViewModel() } val rollViewModel = viewModel { RollViewModel() }
val edit = remember { mutableStateOf<CharacterSheetEditUio?>(null) }
Column( Column(
modifier = Modifier.padding(all = 16.dp), modifier = Modifier.padding(all = 16.dp),
) { ) {
Row {
Button(
onClick = sheetViewModel::showCharacterSheet,
) {
Text(text = "Koryas Tissenpa")
}
IconButton(
onClick = {
edit.value = CharacterSheetEditUio.create(
sheet = CharacterSheetUio.Koryas,
)
}
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = null
)
}
}
Button( Button(
onClick = sheetViewModel::showCharacterSheet, onClick = { edit.value = CharacterSheetEditUio.Default },
) { ) {
Text(text = "Koryas Tissenpa") Text(text = "Créer une feuille de personnage")
} }
} }
@ -82,6 +114,46 @@ fun App() {
} }
} }
} }
edit.value?.let { sheet ->
Window(
onCloseRequest = { edit.value = null },
state = rememberWindowState(
width = 320.dp + 64.dp,
height = 900.dp,
),
title = "LwaCharacterSheet",
) {
Surface(
modifier = Modifier.fillMaxSize(),
) {
CharacterSheetEdit(
form = sheet,
onSkill = { skill ->
edit.value = sheet.copy(
groups = sheet.groups.map { group ->
if (skill.title == group.title) {
group.copy(
fields = mutableListOf<FieldUio>().apply {
addAll(group.fields)
add(
FieldUio.create(
label = "",
valuePlaceHolder = { "40" },
)
)
}
)
} else {
group
}
}
)
}
)
}
}
}
} }
} }
} }

View file

@ -0,0 +1,323 @@
package com.pixelized.desktop.lwa.screen.characterSheet.edit
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.composable.DecoratedBox
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetUio
import kotlin.math.max
import kotlin.math.truncate
@Stable
data class CharacterSheetEditUio(
val name: FieldUio,
val groups: List<Group>,
) {
@Stable
data class Group(
val title: String,
val editable: Boolean = false,
val fields: List<FieldUio>,
)
companion object {
val Default = run {
val strField = FieldUio.create(label = "Force", valuePlaceHolder = { "0" })
fun str(): Int = strField.value.value.toIntOrNull() ?: 0
val dexField = FieldUio.create(label = "Dextérité", valuePlaceHolder = { "0" })
fun dex(): Int = dexField.value.value.toIntOrNull() ?: 0
val conField = FieldUio.create(label = "Constitution", valuePlaceHolder = { "0" })
fun con(): Int = conField.value.value.toIntOrNull() ?: 0
val vitField = FieldUio.create(label = "Taille", valuePlaceHolder = { "0" })
fun vit(): Int = vitField.value.value.toIntOrNull() ?: 0
val intField = FieldUio.create(label = "Intelligence", valuePlaceHolder = { "0" })
fun int(): Int = intField.value.value.toIntOrNull() ?: 0
val powField = FieldUio.create(label = "Pouvoir", valuePlaceHolder = { "0" })
fun pow(): Int = powField.value.value.toIntOrNull() ?: 0
val chaField = FieldUio.create(label = "Charisme", valuePlaceHolder = { "0" })
fun cha(): Int = chaField.value.value.toIntOrNull() ?: 0
CharacterSheetEditUio(
name = FieldUio.create(
useLabelAsPlaceholder = true,
label = "Name",
),
groups = listOf(
Group(
title = "Charactéristiques",
fields = listOf(
strField,
dexField,
conField,
vitField,
intField,
powField,
chaField
),
),
Group(
title = "Charactéristiques dérivées",
fields = listOf(
FieldUio.create(
label = "Déplacement",
valuePlaceHolder = { "10" },
),
FieldUio.create(
label = "Points de vie",
valuePlaceHolder = { "${(con() + vit()) / 2}" },
),
FieldUio.create(
label = "Points de pouvoir",
valuePlaceHolder = { "${pow()}" },
),
FieldUio.create(
label = "Bonus aux dégats",
valuePlaceHolder = {
val bonus = str() + vit()
when {
bonus < 12 -> "-1d6"
bonus in 12..17 -> "-1d4"
bonus in 18..22 -> "-0"
bonus in 23..29 -> "1d4"
bonus in 30..39 -> "1d6"
else -> "2d6"
}
},
),
FieldUio.create(label = "Armure", valuePlaceHolder = { "0" }),
),
),
Group(
title = "Compétances",
editable = true,
fields = listOf(
FieldUio.create(
label = "Bagarre",
valuePlaceHolder = { trunc(dex() * 2) },
),
FieldUio.create(
label = "Esquive",
valuePlaceHolder = { trunc(dex() * 2) }
),
FieldUio.create(
label = "Saisie",
valuePlaceHolder = { trunc(str() + vit()) },
),
FieldUio.create(
label = "Lancer",
valuePlaceHolder = { trunc(str() + dex()) },
),
FieldUio.create(
label = "Athlétisme",
valuePlaceHolder = { trunc(str() + con() * 2) },
),
FieldUio.create(
label = "Acrobatie",
valuePlaceHolder = { trunc(dex() + con() * 2) },
),
FieldUio.create(
label = "Perception",
valuePlaceHolder = { trunc(10 + int() * 2) },
),
FieldUio.create(
label = "Recherche",
valuePlaceHolder = { trunc(10 + int() * 2) },
),
FieldUio.create(
label = "Empathie",
valuePlaceHolder = { trunc(cha() + int()) },
),
FieldUio.create(
label = "Persuasion",
valuePlaceHolder = { trunc(cha() * 3) },
),
FieldUio.create(
label = "Intimidation",
valuePlaceHolder = { trunc(cha() + max(pow(), vit()) * 2) },
),
FieldUio.create(
label = "Baratin",
valuePlaceHolder = { trunc(cha() * 2 + int()) },
),
FieldUio.create(
label = "Marchandage",
valuePlaceHolder = { trunc(cha() * 2) },
),
FieldUio.create(
label = "Discrétion",
valuePlaceHolder = { trunc(cha() + dex() * 2 - vit()) },
),
FieldUio.create(
label = "Escamotage",
valuePlaceHolder = { trunc(dex() * 2) },
),
FieldUio.create(
label = "Premiers soins",
valuePlaceHolder = { trunc(int() + dex()) },
),
),
),
Group(
title = "Occupations",
editable = true,
fields = emptyList(),
),
Group(
title = "Compétences magiques",
editable = true,
fields = emptyList(),
),
)
)
}
fun create(sheet: CharacterSheetUio): CharacterSheetEditUio {
return CharacterSheetEditUio(
name = FieldUio.create(
useLabelAsPlaceholder = true,
label = "Name",
initialValue = sheet.name,
),
groups = listOf(
Group(
title = "Charactéristiques",
fields = sheet.characteristics.map {
FieldUio.create(
label = it.label,
initialValue = it.value,
)
},
),
Group(
title = "Charactéristiques dérivées",
fields = sheet.subCharacteristics.map {
FieldUio.create(
label = it.label,
initialValue = it.value,
)
},
),
Group(
title = "Compétances",
fields = sheet.skills.map {
FieldUio.create(
label = it.label,
initialValue = "${it.value}",
)
},
),
Group(
title = "Occupations",
editable = true,
fields = sheet.occupations.map {
FieldUio.create(
label = it.label,
initialValue = "${it.value}",
)
},
),
Group(
title = "Compétences magiques",
editable = true,
fields = sheet.magics.map {
FieldUio.create(
label = it.label,
initialValue = "${it.value}",
)
},
),
)
)
}
}
}
private fun trunc(value: Int): String {
return "${(truncate(value.toFloat() / 5f) * 5f).toInt()}"
}
@Composable
fun CharacterSheetEdit(
form: CharacterSheetEditUio,
onSkill: (CharacterSheetEditUio.Group) -> Unit,
) {
Column(
modifier = Modifier
.verticalScroll(state = rememberScrollState())
.padding(all = 24.dp),
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
Form(
modifier = Modifier.fillMaxWidth(),
field = form.name,
)
form.groups.forEach {
DecoratedBox(
modifier = Modifier.animateContentSize(),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
modifier = Modifier.padding(vertical = 8.dp),
style = MaterialTheme.typography.caption,
text = it.title,
)
it.fields.forEach {
Form(
modifier = Modifier.fillMaxWidth(),
field = it,
)
}
if (it.editable) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(
space = 8.dp,
alignment = Alignment.End
)
) {
Button(
colors = ButtonDefaults.textButtonColors(),
onClick = { onSkill(it) },
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
Text(
style = MaterialTheme.typography.caption,
text = "Ajouter une ligne",
)
Icon(
imageVector = Icons.Default.Add,
contentDescription = null,
)
}
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,104 @@
package com.pixelized.desktop.lwa.screen.characterSheet.edit
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.unit.dp
@Stable
open class FieldUio(
val useLabelAsPlaceholder: Boolean,
val label: State<String>,
val onLabelChange: (String) -> Unit,
val valuePlaceHolder: State<String>,
val value: State<String>,
val onValueChange: (String) -> Unit,
) {
companion object {
@Stable
fun create(
useLabelAsPlaceholder: Boolean = false,
label: String = "",
initialValue: String = "",
valuePlaceHolder: () -> String = { "" },
): FieldUio {
val labelState = mutableStateOf(label)
val valueState = mutableStateOf(initialValue)
return FieldUio(
useLabelAsPlaceholder = useLabelAsPlaceholder,
label = labelState,
onLabelChange = { labelState.value = it },
valuePlaceHolder = derivedStateOf(valuePlaceHolder),
value = valueState,
onValueChange = { valueState.value = it },
)
}
}
}
@Composable
fun Form(
modifier: Modifier = Modifier,
field: FieldUio,
) {
val focus = LocalFocusManager.current
AnimatedContent(
targetState = field.useLabelAsPlaceholder,
transitionSpec = { fadeIn() togetherWith fadeOut() }
) {
when (it) {
true -> {
TextField(
modifier = modifier,
value = field.value.value,
label = { Text(text = field.label.value) },
singleLine = true,
keyboardActions = KeyboardActions { focus.moveFocus(FocusDirection.Next) },
onValueChange = field.onValueChange,
)
}
else -> {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
TextField(
modifier = Modifier.weight(weight = 1f),
value = field.label.value,
placeholder = { Text(text = "Nom") },
singleLine = true,
keyboardActions = KeyboardActions { focus.moveFocus(FocusDirection.Next) },
onValueChange = field.onLabelChange,
)
TextField(
modifier = Modifier.width(width = 80.dp),
value = field.value.value,
placeholder = { Text(text = field.valuePlaceHolder.value) },
singleLine = true,
keyboardActions = KeyboardActions { focus.moveFocus(FocusDirection.Next) },
onValueChange = field.onValueChange,
)
}
}
}
}
}