diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts
index f01e0d7..846ddb6 100644
--- a/composeApp/build.gradle.kts
+++ b/composeApp/build.gradle.kts
@@ -3,7 +3,6 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
- // alias(libs.plugins.kotlinKtor)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
@@ -22,6 +21,7 @@ kotlin {
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
+ implementation(libs.compose.desktop.preview)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.navigation.compose)
diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index 7763cdc..7e0e41a 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -19,7 +19,7 @@
Création de personnage
Nom
- Ajouter un lancer
+ Ajouter une action
Sauvegarder
Caractéristiques
Force
@@ -35,8 +35,15 @@
Points de pouvoir
Bonus aux dégats
Armure
+ Compétences communes
+ Compétences spéciales
+ Ajouter une compétence spéciale
+ Compétences magiques
+ Ajouter une compétence magique
+ Base
+ Bonus
+ Niveau
Compétences
- Ajouter une compétence
Bagarre
Esquive
Saisie
@@ -57,6 +64,9 @@
Ajouter une occupation
Compétences magiques
Ajouter une compétence magique
+ Supprimer
+ Compétence d'occupation
+
État diminuer
Modifier
@@ -74,9 +84,9 @@
Points de pouvoir
Bonus aux dégats
Armure
- Compétences
- Occupations
- Compétences magiques
+ Compétences communes
+ Compétences spéciales
+ Compétences magiques
Supprimer la feuille de personnage
Êtes-vous sûr de vouloir supprimer "%1$s" ?
Confirmer
@@ -93,7 +103,7 @@
Le Déplacement (DEP) est une valeur de jeu qui détermine la distance que peut parcourir un personnage en un round de combat. Tous les humains ont un DEP de 10. Le DEP a une valeur réelle flexible, mais généralement, chaque point de DEP équivaut à un déplacement d’un mètre. En course, un point équivaut à trois mètres.
Les points de vie (PV) sont égaux à la somme CON+TAI du personnage, divisée par deux (arrondie au supérieur). Ils sont soustraits lorsque le personnage subit des dommages. Quand les points de vie tombent à 0, le personnage sombre dans l’inconscience. S'il reste inconscient trop longtemps, il meurt. Tous les points de vie régénèrent naturellement après une nuit de repos.
les points de pouvoir sont égaux au POU et sont dépensés pour utiliser la magie ou d’autres pouvoirs. Tous les points de pouvoir régénèrent naturellement après une nuit de repos.
- Les personnages plus massifs ou plus forts infligent plus de dégâts quand ils frappent leurs ennemis en combat au corps à corps. Le modificateur s’applique aux dégâts infligés par toute attaque portée par les personnages avec des armes de mêlée. La moitié de ce bonus s'applique aux attaques de lancer.
+ Les personnages plus massifs ou plus forts infligent plus de dégâts quand ils frappent leurs ennemis en combat au corps à corps. Le modificateur s’applique aux dégâts infligés par toute attaque portée par les personnages avec des armes de mêlée (BDC). La moitié de ce bonus s'applique aux attaques de lancer (BDD).
Une armure protège son porteur des blessures. Lorsqu’un personnage est touché en combat par une attaque non magique, soustrayez les points d’armure aux points de dégâts infligés. Les dommages au-delà de la protection de l’armure surpassent celle-ci et sont infligés au personnage, réduisant ses points de vie actuels.
Attaque en combat à mains nues. Une attaque réussie inflige 1D3 + BDGT.
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt
index 3425dcc..2f6a53f 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt
@@ -1,10 +1,14 @@
package com.pixelized.desktop.lwa
+import com.pixelized.desktop.lwa.business.DamageBonusUseCase
+import com.pixelized.desktop.lwa.business.SkillValueComputationUseCase
+import com.pixelized.desktop.lwa.parser.arithmetic.ArithmeticParser
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetJsonFactory
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetStore
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.repository.roll.RollHistoryRepository
+import com.pixelized.desktop.lwa.screen.characterSheet.common.SkillFieldFactory
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetFactory
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetViewModel
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEditFactory
@@ -21,9 +25,11 @@ import org.koin.dsl.module
val moduleDependencies
get() = listOf(
- repositoryDependencies,
+ parserDependencies,
factoryDependencies,
+ repositoryDependencies,
viewModelDependencies,
+ useCaseDependencies,
)
val repositoryDependencies
@@ -40,6 +46,7 @@ val factoryDependencies
factoryOf(::CharacterSheetEditFactory)
factoryOf(::CharacterSheetJsonFactory)
factoryOf(::NetworkFactory)
+ factoryOf(::SkillFieldFactory)
}
val viewModelDependencies
@@ -50,4 +57,15 @@ val viewModelDependencies
viewModelOf(::RollViewModel)
viewModelOf(::RollHistoryViewModel)
viewModelOf(::NetworkViewModel)
- }
\ No newline at end of file
+ }
+
+val parserDependencies
+ get() = module {
+ factoryOf(::ArithmeticParser)
+ }
+
+val useCaseDependencies
+ get() = module {
+ factoryOf(::DamageBonusUseCase)
+ factoryOf(::SkillValueComputationUseCase)
+ }
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/DamageBonusUseCase.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/DamageBonusUseCase.kt
index 553aa76..5aeef35 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/DamageBonusUseCase.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/DamageBonusUseCase.kt
@@ -1,6 +1,6 @@
package com.pixelized.desktop.lwa.business
-object DamageBonusUseCase {
+class DamageBonusUseCase {
fun bonusDamage(strength: Int, height: Int): String {
return bonusDamage(stat = strength + height)
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/SkillValueComputationUseCase.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/SkillValueComputationUseCase.kt
new file mode 100644
index 0000000..fc762a2
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/SkillValueComputationUseCase.kt
@@ -0,0 +1,40 @@
+package com.pixelized.desktop.lwa.business
+
+import com.pixelized.desktop.lwa.parser.arithmetic.Instruction
+import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet
+import kotlin.math.max
+
+class SkillValueComputationUseCase {
+
+ fun computeSkillValue(
+ sheet: CharacterSheet,
+ skill: CharacterSheet.Skill,
+ diminished: Int,
+ ): Int {
+ val baseSum = skill.base.sumOf {
+ when (val instruction = it.instruction) {
+ is Instruction.Dice -> 0
+ is Instruction.Flat -> instruction.value
+ Instruction.Word.BDC -> 0
+ Instruction.Word.BDD -> 0
+ Instruction.Word.STR -> sheet.strength
+ Instruction.Word.DEX -> sheet.dexterity
+ Instruction.Word.CON -> sheet.constitution
+ Instruction.Word.HEI -> sheet.height
+ Instruction.Word.INT -> sheet.intelligence
+ Instruction.Word.POW -> sheet.power
+ Instruction.Word.CHA -> sheet.charisma
+ } * it.sign
+ }
+ val base = if (skill.occupation) {
+ max(MIN_OCCUPATION_VALUE, baseSum)
+ } else {
+ baseSum
+ }
+ return max(base + skill.bonus + skill.level - diminished, 0)
+ }
+
+ companion object {
+ private const val MIN_OCCUPATION_VALUE = 40
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/composable/decoratedBox/DecoratedBox.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/composable/decoratedBox/DecoratedBox.kt
index 69f18e7..0867d42 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/composable/decoratedBox/DecoratedBox.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/composable/decoratedBox/DecoratedBox.kt
@@ -1,19 +1,21 @@
package com.pixelized.desktop.lwa.composable.decoratedBox
+import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.unit.dp
-import com.pixelized.desktop.lwa.theme.LwaTheme
-import org.jetbrains.compose.ui.tooling.preview.Preview
+import com.pixelized.desktop.lwa.utils.preview.ContentPreview
@Composable
fun DecoratedBox(
@@ -34,12 +36,14 @@ fun DecoratedBox(
@Composable
@Preview
private fun DecoratedBoxPreview() {
- LwaTheme {
- Surface {
- DecoratedBox {
- Text("test")
+ ContentPreview {
+ DecoratedBox {
+ Box(
+ modifier = Modifier.width(width = 128.dp).height(64.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ Text("Test")
}
}
}
-}
-
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/WindowNavHost.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/WindowNavHost.kt
index e709679..66e221e 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/WindowNavHost.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/WindowNavHost.kt
@@ -7,7 +7,6 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.window.Window
-import androidx.compose.ui.window.rememberWindowState
import com.pixelized.desktop.lwa.navigation.window.destination.Window
val LocalWindow = compositionLocalOf {
@@ -43,10 +42,7 @@ fun WindowsNavHost(
) {
Window(
onCloseRequest = { controller.hideWindow(id = window.id) },
- state = rememberWindowState(
- width = window.width,
- height = window.height,
- ),
+ state = window.state,
title = window.title,
) {
content.invoke(window)
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/destination/CharacterSheetCreateWindow.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/destination/CharacterSheetCreateWindow.kt
index ff5e966..fa5ef18 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/destination/CharacterSheetCreateWindow.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/destination/CharacterSheetCreateWindow.kt
@@ -1,6 +1,15 @@
package com.pixelized.desktop.lwa.navigation.window.destination
import androidx.compose.runtime.Stable
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
@Stable
-class CharacterSheetCreateWindow : Window(title = "")
\ No newline at end of file
+class CharacterSheetCreateWindow : Window(
+ title = "",
+ size = size,
+) {
+ companion object {
+ val size = DpSize(600.dp, 900.dp)
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/destination/CharacterSheetWindow.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/destination/CharacterSheetWindow.kt
index ee7009b..e841a92 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/destination/CharacterSheetWindow.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/destination/CharacterSheetWindow.kt
@@ -1,6 +1,8 @@
package com.pixelized.desktop.lwa.navigation.window.destination
import androidx.compose.runtime.Stable
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
@Stable
class CharacterSheetWindow(
@@ -8,5 +10,13 @@ class CharacterSheetWindow(
characterName: String,
) : Window(
title = characterName,
-)
+ size = size,
+) {
+ companion object {
+ val size = DpSize(
+ width = 400.dp + 64.dp,
+ height = 900.dp,
+ )
+ }
+}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/destination/Window.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/destination/Window.kt
index c3412bb..eea81b4 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/destination/Window.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/window/destination/Window.kt
@@ -2,13 +2,31 @@ package com.pixelized.desktop.lwa.navigation.window.destination
import androidx.compose.runtime.Stable
import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.window.WindowPlacement
+import androidx.compose.ui.window.WindowPosition
+import androidx.compose.ui.window.WindowState
import java.util.UUID
@Stable
sealed class Window(
val id: String = UUID.randomUUID().toString(),
val title: String,
- val width: Dp = 400.dp + 64.dp,
- val height: Dp = 900.dp,
-)
\ No newline at end of file
+ size: DpSize,
+) {
+ val state = WindowState(
+ placement = WindowPlacement.Floating,
+ isMinimized = false,
+ position = WindowPosition.PlatformDefault,
+ width = size.width,
+ height = size.height,
+ )
+
+ val width: Dp get() = state.size.width
+ val height: Dp get() = state.size.height
+ val size: DpSize get() = state.size
+
+ suspend fun resize(size: DpSize) {
+ state.size = size
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/parser/arithmetic/ArithmeticParser.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/parser/arithmetic/ArithmeticParser.kt
new file mode 100644
index 0000000..1bac89c
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/parser/arithmetic/ArithmeticParser.kt
@@ -0,0 +1,112 @@
+package com.pixelized.desktop.lwa.parser.arithmetic
+
+import com.pixelized.desktop.lwa.parser.arithmetic.Instruction.Dice.Modifier
+import com.pixelized.desktop.lwa.parser.arithmetic.Instruction.Word
+
+// https://medium.com/@gustavoinzunza/how-to-solve-balanced-parenthesis-problem-with-regex-in-ruby-113eff30a79a
+// https://stackoverflow.com/questions/546433/regular-expression-to-match-balanced-parentheses
+class ArithmeticParser {
+
+ private val operatorParser = Regex(
+ """\s*(?[-+])?\s*(?[^-+]*)"""
+ )
+ private val diceParser = Regex(
+ """^(?[ade])?(?\d+)[dD](?\d+)"""
+ )
+ private val wordParser = Regex(
+ """^(?${words.joinToString(separator = "|") { it }})"""
+ )
+ private val flatParser = Regex(
+ """(?\d+)"""
+ )
+
+ @Throws(Instruction.UnknownInstruction::class)
+ fun parse(value: String): List {
+ return operatorParser.findAll(value).mapNotNull {
+ val (operator, instruction) = it.destructured
+ if (instruction.isNotBlank()) {
+ Arithmetic(
+ sign = parseOperator(operator),
+ instruction = parseInstruction(instruction),
+ )
+ } else {
+ null
+ }
+ }.toList()
+ }
+
+ @Throws(Instruction.UnknownInstruction::class)
+ fun parseInstruction(instruction: String): Instruction {
+ diceParser.find(instruction)?.let {
+ val (modifier, quantity, faces) = it.destructured
+ Instruction.Dice(
+ modifier = parseModifier(value = modifier),
+ quantity = quantity.toInt(),
+ faces = faces.toInt(),
+ )
+ }?.let { return it }
+
+ wordParser.find(instruction)?.let {
+ parseWord(value = it.value)
+ }?.let { return it }
+
+ flatParser.find(instruction)?.let {
+ Instruction.Flat(value = it.value.toInt())
+ }?.let { return it }
+
+ throw Instruction.UnknownInstruction(payload = instruction)
+ }
+
+ private fun parseOperator(operator: String): Int {
+ return when (operator) {
+ "-" -> -1
+ "+" -> 1
+ else -> 1
+ }
+ }
+
+ private fun parseModifier(value: String): Modifier? {
+ return when (value) {
+ "a" -> Modifier.ADVANTAGE
+ "d" -> Modifier.DISADVANTAGE
+ "e" -> Modifier.EMPHASIS
+ else -> null
+ }
+ }
+
+ private fun parseWord(value: String): Word? {
+ return try {
+ Word.valueOf(value)
+ } catch (_: Exception) {
+ return null
+ }
+ }
+
+ fun convertInstructionToString(
+ instructions: List,
+ ): String {
+ return instructions.map {
+ val sign = if (it.sign > 0) "+" else "-"
+ val value = when (val instruction = it.instruction) {
+ is Instruction.Dice -> "${instruction.modifier ?: ""}${instruction.quantity}d${instruction.faces}"
+ is Instruction.Flat -> "${instruction.value}"
+ Word.BDC -> Word.BDC.name
+ Word.BDD -> Word.BDD.name
+ Word.STR -> Word.STR.name
+ Word.DEX -> Word.DEX.name
+ Word.CON -> Word.CON.name
+ Word.HEI -> Word.HEI.name
+ Word.INT -> Word.INT.name
+ Word.POW -> Word.POW.name
+ Word.CHA -> Word.CHA.name
+ }
+ "$sign$value"
+ }.joinToString(separator = " ") {
+ it
+ }
+ }
+
+ companion object {
+ val words: List = Word.entries.map { it.name }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/parser/arithmetic/Instruction.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/parser/arithmetic/Instruction.kt
new file mode 100644
index 0000000..8d254d4
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/parser/arithmetic/Instruction.kt
@@ -0,0 +1,40 @@
+package com.pixelized.desktop.lwa.parser.arithmetic
+
+class Arithmetic(
+ val sign: Int,
+ val instruction: Instruction,
+)
+
+sealed interface Instruction {
+ data class Dice(
+ val modifier: Modifier?,
+ val quantity: Int,
+ val faces: Int,
+ ) : Instruction {
+ enum class Modifier {
+ ADVANTAGE,
+ DISADVANTAGE,
+ EMPHASIS,
+ }
+ }
+
+ data class Flat(
+ val value: Int,
+ ) : Instruction
+
+ enum class Word : Instruction {
+ BDC, // Damages bonus for melee
+ BDD, // Damages bonus for range
+ STR, // Strength
+ DEX, // Dexterity
+ CON, // Constitution
+ HEI, // Height
+ INT, // Intelligence
+ POW, // Power
+ CHA, // Charisma
+ }
+
+ class UnknownInstruction(payload: String) : RuntimeException(
+ "Unknown instruction exception. Unable to parse the following payload:\"$payload\" into an instruction"
+ )
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetJsonFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetJsonFactory.kt
index ffb2468..870156b 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetJsonFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetJsonFactory.kt
@@ -1,12 +1,18 @@
package com.pixelized.desktop.lwa.repository.characterSheet
+import com.pixelized.desktop.lwa.business.DamageBonusUseCase
+import com.pixelized.desktop.lwa.parser.arithmetic.ArithmeticParser
import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet
import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheetJson
import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheetJsonV1
+import kotlin.math.ceil
-class CharacterSheetJsonFactory {
+class CharacterSheetJsonFactory(
+ private val bonusDamageUseCase: DamageBonusUseCase,
+ private val arithmeticParser: ArithmeticParser,
+) {
- fun convertLastJsonVersion(
+ fun convertToJson(
sheet: CharacterSheet,
): CharacterSheetJson {
val json = CharacterSheetJsonV1(
@@ -19,38 +25,51 @@ class CharacterSheetJsonFactory {
intelligence = sheet.intelligence,
power = sheet.power,
charisma = sheet.charisma,
- movement = sheet.movement,
+ movement = if (sheet.overrideMovement) sheet.movement else null,
currentHp = sheet.currentHp,
- maxHp = sheet.maxHp,
- currentPP = sheet.currentPP,
- maxPP = sheet.maxPP,
- damageBonus = sheet.damageBonus,
- armor = sheet.armor,
- skills = sheet.skills.map {
+ maxHp = if (sheet.overrideMaxHp) sheet.maxHp else null,
+ currentPP = sheet.currentPp,
+ maxPP = if (sheet.overrideMaxPP) sheet.maxPP else null,
+ damageBonus = if (sheet.overrideDamageBonus) sheet.damageBonus else null,
+ armor = if (sheet.overrideArmor) sheet.armor else null,
+ skills = sheet.commonSkills.map {
CharacterSheetJsonV1.Skill(
+ id = it.id,
label = it.label,
- value = it.value,
+ base = arithmeticParser.convertInstructionToString(it.base),
+ bonus = it.bonus,
+ level = it.level,
+ occupation = it.occupation,
used = it.used,
)
},
- occupations = sheet.occupations.map {
+ occupations = sheet.specialSkills.map {
CharacterSheetJsonV1.Skill(
+ id = it.id,
label = it.label,
- value = it.value,
+ base = arithmeticParser.convertInstructionToString(it.base),
+ bonus = it.bonus,
+ level = it.level,
+ occupation = it.occupation,
used = it.used,
)
},
- magics = sheet.magics.map {
+ magics = sheet.magicSkills.map {
CharacterSheetJsonV1.Skill(
+ id = it.id,
label = it.label,
- value = it.value,
+ base = arithmeticParser.convertInstructionToString(it.base),
+ bonus = it.bonus,
+ level = it.level,
+ occupation = it.occupation,
used = it.used,
)
},
- rolls = sheet.rolls.map {
+ rolls = sheet.actions.map {
CharacterSheetJsonV1.Roll(
+ id = it.id,
label = it.label,
- roll = it.roll,
+ roll = arithmeticParser.convertInstructionToString(it.roll),
)
},
)
@@ -78,38 +97,60 @@ class CharacterSheetJsonFactory {
intelligence = json.intelligence,
power = json.power,
charisma = json.charisma,
- movement = json.movement,
+ overrideMovement = json.movement != null,
+ movement = json.movement ?: 10,
currentHp = json.currentHp,
- maxHp = json.maxHp,
- currentPP = json.currentPP,
- maxPP = json.maxPP,
- damageBonus = json.damageBonus,
- armor = json.armor,
- skills = json.skills.map {
+ overrideMaxHp = json.maxHp != null,
+ maxHp = json.maxHp ?: (ceil((json.constitution + json.height) / 2f).toInt()),
+ currentPp = json.currentPP,
+ overrideMaxPP = json.maxPP != null,
+ maxPP = json.maxPP ?: json.power,
+ overrideDamageBonus = json.damageBonus != null,
+ damageBonus = json.damageBonus
+ ?: bonusDamageUseCase.bonusDamage(
+ strength = json.strength,
+ height = json.height
+ ),
+ overrideArmor = json.armor != null,
+ armor = json.armor ?: 0,
+ commonSkills = json.skills.map {
CharacterSheet.Skill(
+ id = it.id,
label = it.label,
- value = it.value,
+ base = arithmeticParser.parse(it.base),
+ bonus = it.bonus,
+ level = it.level,
+ occupation = it.occupation,
used = it.used,
)
},
- occupations = json.occupations.map {
+ specialSkills = json.occupations.map {
CharacterSheet.Skill(
+ id = it.id,
label = it.label,
- value = it.value,
+ base = arithmeticParser.parse(it.base),
+ bonus = it.bonus,
+ level = it.level,
+ occupation = it.occupation,
used = it.used,
)
},
- magics = json.magics.map {
+ magicSkills = json.magics.map {
CharacterSheet.Skill(
+ id = it.id,
label = it.label,
- value = it.value,
+ base = arithmeticParser.parse(it.base),
+ bonus = it.bonus,
+ level = it.level,
+ occupation = it.occupation,
used = it.used,
)
},
- rolls = json.rolls.map {
+ actions = json.rolls.map {
CharacterSheet.Roll(
+ id = it.id,
label = it.label,
- roll = it.roll,
+ roll = arithmeticParser.parse(it.roll),
)
},
)
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt
index df50663..29b9856 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt
@@ -26,7 +26,7 @@ class CharacterSheetStore(
fun save(sheets: List) {
val json = try {
sheets
- .map(factory::convertLastJsonVersion)
+ .map(factory::convertToJson)
.let(Json::encodeToString)
} catch (exception: Exception) {
throw JsonConversionException(root = exception)
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheet.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheet.kt
index 34fe597..47a2473 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheet.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheet.kt
@@ -1,5 +1,7 @@
package com.pixelized.desktop.lwa.repository.characterSheet.model
+import com.pixelized.desktop.lwa.parser.arithmetic.Arithmetic
+
data class CharacterSheet(
val id: String,
val name: String,
@@ -12,49 +14,57 @@ data class CharacterSheet(
val power: Int,
val charisma: Int,
// sub characteristics
+ val overrideMovement: Boolean,
val movement: Int,
val currentHp: Int,
+ val overrideMaxHp: Boolean,
val maxHp: Int,
- val currentPP: Int,
+ val currentPp: Int,
+ val overrideMaxPP: Boolean,
val maxPP: Int,
+ val overrideDamageBonus: Boolean,
val damageBonus: String,
+ val overrideArmor: Boolean,
val armor: Int,
// skills
- val skills: List,
- // occupations
- val occupations: List,
- // magic skill
- val magics: List,
- // attack
- val rolls: List,
+ val commonSkills: List,
+ val specialSkills: List,
+ val magicSkills: List,
+ // actions
+ val actions: List,
) {
data class Skill(
+ val id: String,
val label: String,
- val value: Int,
+ val base: List,
+ val bonus: Int,
+ val level: Int,
+ val occupation: Boolean,
val used: Boolean,
)
data class Roll(
+ val id: String,
val label: String,
- val roll: String,
+ val roll: List,
)
- companion object {
- const val COMBAT = "Bagarre"
- const val DODGE = "Esquive"
- const val GRAB = "Saisie"
- const val THROW = "Lancer"
- const val ATHLETICS = "Athlétisme"
- const val ACROBATICS = "Acrobatie"
- const val PERCEPTION = "Perception"
- const val SEARCH = "Recherche"
- const val EMPATHY = "Empathie"
- const val PERSUASION = "Persuasion"
- const val INTIMIDATION = "Intimidation"
- const val SPIEL = "Baratin"
- const val BARGAIN = "Marchandage"
- const val DISCRETION = "Discrétion"
- const val SLEIGHT_OF_HAND = "Escamotage"
- const val AID = "Premiers soins"
+ object CommonSkillId {
+ const val COMBAT_ID = "Bagarre"
+ const val DODGE_ID = "Esquive"
+ const val GRAB_ID = "Saisie"
+ const val THROW_ID = "Lancer"
+ const val ATHLETICS_ID = "Athlétisme"
+ const val ACROBATICS_ID = "Acrobatie"
+ const val PERCEPTION_ID = "Perception"
+ const val SEARCH_ID = "Recherche"
+ const val EMPATHY_ID = "Empathie"
+ const val PERSUASION_ID = "Persuasion"
+ const val INTIMIDATION_ID = "Intimidation"
+ const val SPIEL_ID = "Baratin"
+ const val BARGAIN_ID = "Marchandage"
+ const val DISCRETION_ID = "Discrétion"
+ const val SLEIGHT_OF_HAND_ID = "Escamotage"
+ const val AID_ID = "Premiers soins"
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJsonV1.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJsonV1.kt
index a342f39..3219c2e 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJsonV1.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJsonV1.kt
@@ -15,13 +15,13 @@ data class CharacterSheetJsonV1(
val power: Int,
val charisma: Int,
// sub characteristics
- val movement: Int,
+ val movement: Int?,
val currentHp: Int,
- val maxHp: Int,
+ val maxHp: Int?,
val currentPP: Int,
- val maxPP: Int,
- val damageBonus: String,
- val armor: Int,
+ val maxPP: Int?,
+ val damageBonus: String?,
+ val armor: Int?,
// skills
val skills: List,
// occupations
@@ -33,33 +33,19 @@ data class CharacterSheetJsonV1(
) : CharacterSheetJson {
@Serializable
data class Skill(
+ val id: String,
val label: String,
- val value: Int,
+ val base: String,
+ val bonus: Int,
+ val level: Int,
+ val occupation: Boolean,
val used: Boolean,
)
@Serializable
data class Roll(
+ val id: String,
val label: String,
val roll: String,
)
-
- companion object {
- const val COMBAT = "Bagarre"
- const val DODGE = "Esquive"
- const val GRAB = "Saisie"
- const val THROW = "Lancer"
- const val ATHLETICS = "Athlétisme"
- const val ACROBATICS = "Acrobatie"
- const val PERCEPTION = "Perception"
- const val SEARCH = "Recherche"
- const val EMPATHY = "Empathie"
- const val PERSUASION = "Persuasion"
- const val INTIMIDATION = "Intimidation"
- const val SPIEL = "Baratin"
- const val BARGAIN = "Marchandage"
- const val DISCRETION = "Discrétion"
- const val SLEIGHT_OF_HAND = "Escamotage"
- const val AID = "Premiers soins"
- }
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/common/SkillFieldFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/common/SkillFieldFactory.kt
new file mode 100644
index 0000000..eb1dbbb
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/common/SkillFieldFactory.kt
@@ -0,0 +1,86 @@
+package com.pixelized.desktop.lwa.screen.characterSheet.common
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.runtime.mutableStateOf
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.SkillFieldUio
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.textfield.TextFieldWrapperUio
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.option.ActionOption
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.option.CheckedOption
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.option.OptionUio
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__delete__label
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__occupation__label
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__base_label
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__bonus_label
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__level_label
+import org.jetbrains.compose.resources.getString
+import java.util.UUID
+
+class SkillFieldFactory {
+
+
+ suspend fun createSkill(
+ id: String = UUID.randomUUID().toString(),
+ label: String,
+ labelValue: String = "",
+ baseValue: String = "",
+ bonusValue: String = "",
+ levelValue: String = "",
+ options: List = emptyList(),
+ ): SkillFieldUio {
+ return SkillFieldUio(
+ id = id,
+ label = createWrapper(
+ label = label,
+ value = labelValue,
+ ),
+ base = createWrapper(
+ label = getString(Res.string.character_sheet_edit__skills__base_label),
+ value = baseValue,
+ ),
+ bonus = createWrapper(
+ label = getString(Res.string.character_sheet_edit__skills__bonus_label),
+ value = bonusValue,
+ ),
+ level = createWrapper(
+ label = getString(Res.string.character_sheet_edit__skills__level_label),
+ value = levelValue,
+ ),
+ options = options,
+ )
+ }
+
+ fun createWrapper(
+ enable: Boolean = true,
+ label: String = "",
+ value: String = "",
+ ): TextFieldWrapperUio {
+ val state = mutableStateOf(value)
+ return TextFieldWrapperUio(
+ enable = enable,
+ label = label,
+ value = state,
+ onValueChange = { state.value = it },
+ )
+ }
+
+ suspend fun deleteOption(onDelete: () -> Unit) = ActionOption.DeleteOptionUio(
+ icon = Icons.Default.Delete,
+ label = getString(Res.string.character_sheet_edit__delete__label),
+ onOption = onDelete,
+ )
+
+ suspend fun occupationOption(checked: Boolean) = mutableStateOf(checked).let { state ->
+ CheckedOption.OccupationOption(
+ checked = state,
+ label = getString(Res.string.character_sheet_edit__occupation__label),
+ onOption = { state.value = state.value.not() },
+ )
+ }
+}
+
+val List.occupation: Boolean
+ get() = this.firstNotNullOfOrNull {
+ if (it is CheckedOption.OccupationOption) it.checked.value else null
+ } ?: false
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetFactory.kt
index 1502be5..31e7a24 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetFactory.kt
@@ -1,7 +1,9 @@
package com.pixelized.desktop.lwa.screen.characterSheet.detail
+import com.pixelized.desktop.lwa.business.SkillValueComputationUseCase
import com.pixelized.desktop.lwa.composable.tooltip.TooltipUio
import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet
+import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio.Characteristic
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio.Node
import lwacharactersheet.composeapp.generated.resources.Res
@@ -46,9 +48,10 @@ import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteris
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__movement
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__power_point
import org.jetbrains.compose.resources.getString
-import kotlin.math.max
-class CharacterSheetFactory {
+class CharacterSheetFactory(
+ private val skillUseCase: SkillValueComputationUseCase,
+) {
companion object {
const val HP = "HP"
@@ -56,17 +59,17 @@ class CharacterSheetFactory {
}
suspend fun convertToUio(
- model: CharacterSheet,
+ sheet: CharacterSheet,
diminished: Int,
): CharacterSheetPageUio {
return CharacterSheetPageUio(
- id = model.id,
- name = model.name,
+ id = sheet.id,
+ name = sheet.name,
characteristics = listOf(
Characteristic(
id = "STR",
label = getString(Res.string.character_sheet__characteristics__str),
- value = "${model.strength}",
+ value = "${sheet.strength}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__str),
description = getString(Res.string.tooltip__characteristics__strength),
@@ -76,7 +79,7 @@ class CharacterSheetFactory {
Characteristic(
id = "DEX",
label = getString(Res.string.character_sheet__characteristics__dex),
- value = "${model.dexterity}",
+ value = "${sheet.dexterity}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__dex),
description = getString(Res.string.tooltip__characteristics__dexterity),
@@ -86,7 +89,7 @@ class CharacterSheetFactory {
Characteristic(
id = "CON",
label = getString(Res.string.character_sheet__characteristics__con),
- value = "${model.constitution}",
+ value = "${sheet.constitution}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__con),
description = getString(Res.string.tooltip__characteristics__constitution),
@@ -96,7 +99,7 @@ class CharacterSheetFactory {
Characteristic(
id = "HEI",
label = getString(Res.string.character_sheet__characteristics__hei),
- value = "${model.height}",
+ value = "${sheet.height}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__hei),
description = getString(Res.string.tooltip__characteristics__height),
@@ -106,7 +109,7 @@ class CharacterSheetFactory {
Characteristic(
id = "INT",
label = getString(Res.string.character_sheet__characteristics__int),
- value = "${model.intelligence}",
+ value = "${sheet.intelligence}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__int),
description = getString(Res.string.tooltip__characteristics__intelligence),
@@ -116,7 +119,7 @@ class CharacterSheetFactory {
Characteristic(
id = "POW",
label = getString(Res.string.character_sheet__characteristics__pow),
- value = "${model.power}",
+ value = "${sheet.power}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__pow),
description = getString(Res.string.tooltip__characteristics__power),
@@ -126,7 +129,7 @@ class CharacterSheetFactory {
Characteristic(
id = "CHA",
label = getString(Res.string.character_sheet__characteristics__cha),
- value = "${model.charisma}",
+ value = "${sheet.charisma}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__cha),
description = getString(Res.string.tooltip__characteristics__charisma),
@@ -138,7 +141,7 @@ class CharacterSheetFactory {
Characteristic(
id = "MOV",
label = getString(Res.string.character_sheet__sub_characteristics__movement),
- value = "${model.movement}",
+ value = "${sheet.movement}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__movement),
description = getString(Res.string.tooltip__sub_characteristics__movement),
@@ -148,7 +151,7 @@ class CharacterSheetFactory {
Characteristic(
id = HP,
label = getString(Res.string.character_sheet__sub_characteristics__hit_point),
- value = "${model.currentHp}/${model.maxHp}",
+ value = "${sheet.currentHp}/${sheet.maxHp}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__hit_point),
description = getString(Res.string.tooltip__sub_characteristics__hit_point),
@@ -158,7 +161,7 @@ class CharacterSheetFactory {
Characteristic(
id = PP,
label = getString(Res.string.character_sheet__sub_characteristics__power_point),
- value = "${model.currentPP}/${model.maxPP}",
+ value = "${sheet.currentPp}/${sheet.maxPP}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__power_point),
description = getString(Res.string.tooltip__sub_characteristics__power_point),
@@ -168,7 +171,7 @@ class CharacterSheetFactory {
Characteristic(
id = "DMG",
label = getString(Res.string.character_sheet__sub_characteristics__damage_bonus),
- value = model.damageBonus,
+ value = sheet.damageBonus,
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__damage_bonus),
description = getString(Res.string.tooltip__sub_characteristics__bonus_damage),
@@ -178,7 +181,7 @@ class CharacterSheetFactory {
Characteristic(
id = "ARMOR",
label = getString(Res.string.character_sheet__sub_characteristics__armor),
- value = "${model.armor}",
+ value = "${sheet.armor}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__armor),
description = getString(Res.string.tooltip__sub_characteristics__armor),
@@ -186,69 +189,69 @@ class CharacterSheetFactory {
editable = false,
),
),
- skills = model.skills.mapNotNull { skill ->
- if (skill.value > 0) {
- val description = when (skill.label) {
- CharacterSheet.COMBAT -> getString(Res.string.tooltip__skills__combat)
- CharacterSheet.DODGE -> getString(Res.string.tooltip__skills__dodge)
- CharacterSheet.GRAB -> getString(Res.string.tooltip__skills__grab)
- CharacterSheet.THROW -> getString(Res.string.tooltip__skills__throw)
- CharacterSheet.ATHLETICS -> getString(Res.string.tooltip__skills__athletics)
- CharacterSheet.ACROBATICS -> getString(Res.string.tooltip__skills__acrobatics)
- CharacterSheet.PERCEPTION -> getString(Res.string.tooltip__skills__perception)
- CharacterSheet.SEARCH -> getString(Res.string.tooltip__skills__search)
- CharacterSheet.EMPATHY -> getString(Res.string.tooltip__skills__empathy)
- CharacterSheet.PERSUASION -> getString(Res.string.tooltip__skills__persuasion)
- CharacterSheet.INTIMIDATION -> getString(Res.string.tooltip__skills__intimidation)
- CharacterSheet.SPIEL -> getString(Res.string.tooltip__skills__spiel)
- CharacterSheet.BARGAIN -> getString(Res.string.tooltip__skills__bargain)
- CharacterSheet.DISCRETION -> getString(Res.string.tooltip__skills__discretion)
- CharacterSheet.SLEIGHT_OF_HAND -> getString(Res.string.tooltip__skills__sleight_of_hand)
- CharacterSheet.AID -> getString(Res.string.tooltip__skills__aid)
- else -> null
- }
- Node(
- label = skill.label,
- value = skill.value.diminished(diminished),
- tooltips = description?.let {
- TooltipUio(
- title = skill.label,
- description = it,
- )
- },
- used = skill.used,
- )
- } else {
- null
+ commonSkills = sheet.commonSkills.map { skill ->
+ val description = when (skill.id) {
+ CommonSkillId.COMBAT_ID -> getString(Res.string.tooltip__skills__combat)
+ CommonSkillId.DODGE_ID -> getString(Res.string.tooltip__skills__dodge)
+ CommonSkillId.GRAB_ID -> getString(Res.string.tooltip__skills__grab)
+ CommonSkillId.THROW_ID -> getString(Res.string.tooltip__skills__throw)
+ CommonSkillId.ATHLETICS_ID -> getString(Res.string.tooltip__skills__athletics)
+ CommonSkillId.ACROBATICS_ID -> getString(Res.string.tooltip__skills__acrobatics)
+ CommonSkillId.PERCEPTION_ID -> getString(Res.string.tooltip__skills__perception)
+ CommonSkillId.SEARCH_ID -> getString(Res.string.tooltip__skills__search)
+ CommonSkillId.EMPATHY_ID -> getString(Res.string.tooltip__skills__empathy)
+ CommonSkillId.PERSUASION_ID -> getString(Res.string.tooltip__skills__persuasion)
+ CommonSkillId.INTIMIDATION_ID -> getString(Res.string.tooltip__skills__intimidation)
+ CommonSkillId.SPIEL_ID -> getString(Res.string.tooltip__skills__spiel)
+ CommonSkillId.BARGAIN_ID -> getString(Res.string.tooltip__skills__bargain)
+ CommonSkillId.DISCRETION_ID -> getString(Res.string.tooltip__skills__discretion)
+ CommonSkillId.SLEIGHT_OF_HAND_ID -> getString(Res.string.tooltip__skills__sleight_of_hand)
+ CommonSkillId.AID_ID -> getString(Res.string.tooltip__skills__aid)
+ else -> null
}
+ Node(
+ label = skill.label,
+ value = skillUseCase.computeSkillValue(
+ sheet = sheet,
+ skill = skill,
+ diminished = diminished,
+ ),
+ tooltips = description?.let {
+ TooltipUio(
+ title = skill.label,
+ description = it,
+ )
+ },
+ used = skill.used,
+ )
},
- occupations = model.occupations.mapNotNull {
- if (it.value > 0) {
- Node(
- label = it.label,
- value = it.value.diminished(diminished),
- used = it.used,
- )
- } else {
- null
- }
+ specialSKills = sheet.specialSkills.map {
+ Node(
+ label = it.label,
+ value = skillUseCase.computeSkillValue(
+ sheet = sheet,
+ skill = it,
+ diminished = diminished,
+ ),
+ used = it.used,
+ )
},
- magics = model.magics.mapNotNull {
- if (it.value > 0) {
- Node(
- label = it.label,
- value = it.value.diminished(diminished),
- used = it.used,
- )
- } else {
- null
- }
+ magicsSkills = sheet.magicSkills.map {
+ Node(
+ label = it.label,
+ value = skillUseCase.computeSkillValue(
+ sheet = sheet,
+ skill = it,
+ diminished = diminished,
+ ),
+ used = it.used,
+ )
},
- rolls = model.rolls.mapNotNull {
+ actions = sheet.actions.mapNotNull {
if (it.roll.isNotEmpty()) {
CharacterSheetPageUio.Roll(
label = it.label,
- value = it.roll,
+ value = "TODO",//it.roll,
)
} else {
null
@@ -256,8 +259,4 @@ class CharacterSheetFactory {
}
)
}
-}
-
-fun Int.diminished(value: Int): Int {
- return max(0, this - value)
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt
index b160a4a..64c5b52 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt
@@ -41,6 +41,7 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
@@ -61,6 +62,7 @@ import com.pixelized.desktop.lwa.composable.tooltip.TooltipUio
import com.pixelized.desktop.lwa.navigation.screen.LocalScreenController
import com.pixelized.desktop.lwa.navigation.screen.destination.navigateToCharacterSheetEdit
import com.pixelized.desktop.lwa.navigation.window.LocalWindow
+import com.pixelized.desktop.lwa.navigation.window.destination.CharacterSheetWindow
import com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog.CharacterSheetDeleteConfirmationDialog
import com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog.CharacterSheetStatDialog
import com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog.DiminishedStatDialog
@@ -70,9 +72,9 @@ import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet__delete__label
import lwacharactersheet.composeapp.generated.resources.character_sheet__edit__label
-import lwacharactersheet.composeapp.generated.resources.character_sheet__magics__title
-import lwacharactersheet.composeapp.generated.resources.character_sheet__occupations_title
-import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__title
+import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__common_title
+import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__magic_title
+import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__special_title
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__title
import lwacharactersheet.composeapp.generated.resources.ic_d20_32dp
import lwacharactersheet.composeapp.generated.resources.ic_skull_32dp
@@ -86,10 +88,10 @@ data class CharacterSheetPageUio(
val name: String,
val characteristics: List,
val subCharacteristics: List,
- val skills: List,
- val occupations: List,
- val magics: List,
- val rolls: List,
+ val commonSkills: List,
+ val specialSKills: List,
+ val magicsSkills: List,
+ val actions: List,
) {
@Stable
data class Characteristic(
@@ -126,6 +128,10 @@ fun CharacterSheetPage(
val scope = rememberCoroutineScope()
val blurController = remember { BlurContentController() }
+ LaunchedEffect(Unit) {
+ window.resize(size = CharacterSheetWindow.size)
+ }
+
Surface(
modifier = Modifier.fillMaxSize(),
) {
@@ -365,7 +371,7 @@ fun CharacterSheetPageContent(
modifier = Modifier
.verticalScroll(state = scrollState)
.padding(paddingValues)
- .padding(horizontal = 24.dp, vertical = 16.dp),
+ .padding(all = 16.dp),
horizontalArrangement = Arrangement.spacedBy(space = 16.dp),
) {
Column(
@@ -427,9 +433,9 @@ fun CharacterSheetPageContent(
.padding(bottom = 8.dp),
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
- text = stringResource(Res.string.character_sheet__skills__title),
+ text = stringResource(Res.string.character_sheet__skills__common_title),
)
- characterSheet.skills.forEach { skill ->
+ characterSheet.commonSkills.forEach { skill ->
Skill(
modifier = Modifier.cell(),
node = skill,
@@ -449,9 +455,9 @@ fun CharacterSheetPageContent(
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
- text = stringResource(Res.string.character_sheet__occupations_title),
+ text = stringResource(Res.string.character_sheet__skills__special_title),
)
- characterSheet.occupations.forEach { occupation ->
+ characterSheet.specialSKills.forEach { occupation ->
Skill(
modifier = Modifier.cell(),
node = occupation,
@@ -471,9 +477,9 @@ fun CharacterSheetPageContent(
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
- text = stringResource(Res.string.character_sheet__magics__title),
+ text = stringResource(Res.string.character_sheet__skills__magic_title),
)
- characterSheet.magics.forEach { magic ->
+ characterSheet.magicsSkills.forEach { magic ->
Skill(
modifier = Modifier.cell(),
node = magic,
@@ -483,7 +489,7 @@ fun CharacterSheetPageContent(
}
}
}
- characterSheet.rolls.forEach { roll ->
+ characterSheet.actions.forEach { roll ->
Roll(
modifier = Modifier.cell(),
label = roll.label,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetViewModel.kt
index 3cc9e89..6bc3d7e 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetViewModel.kt
@@ -73,7 +73,7 @@ class CharacterSheetViewModel(
}
).collectAsState { (sheet, diminished) ->
sheet?.let { model ->
- runBlocking { factory.convertToUio(model = model, diminished = diminished) }
+ runBlocking { factory.convertToUio(sheet = model, diminished = diminished) }
}
}
@@ -84,21 +84,21 @@ class CharacterSheetViewModel(
fun onUseSkill(skill: CharacterSheetPageUio.Node) {
repository.characterSheetFlow(id = argument.id).value?.let { sheet ->
- val skills = sheet.skills.map {
+ val skills = sheet.commonSkills.map {
if (it.label == skill.label) it.copy(used = it.used.not()) else it
}
- val occupations = sheet.occupations.map {
+ val occupations = sheet.specialSkills.map {
if (it.label == skill.label) it.copy(used = it.used.not()) else it
}
- val magics = sheet.magics.map {
+ val magics = sheet.magicSkills.map {
if (it.label == skill.label) it.copy(used = it.used.not()) else it
}
repository.save(
characterSheet = sheet.copy(
- skills = skills,
- occupations = occupations,
- magics = magics,
+ commonSkills = skills,
+ specialSkills = occupations,
+ magicSkills = magics,
)
)
}
@@ -137,7 +137,7 @@ class CharacterSheetViewModel(
CharacterSheetFactory.PP -> {
val value = mutableStateOf(
- "${sheet.currentPP}".let {
+ "${sheet.currentPp}".let {
TextFieldValue(text = it, selection = TextRange(it.length))
}
)
@@ -166,7 +166,7 @@ class CharacterSheetViewModel(
val sheet = repository.characterSheetFlow(id = argument.id).value
val updated = when (characteristicId) {
CharacterSheetFactory.HP -> sheet?.copy(currentHp = max(0, min(sheet.maxHp, value)))
- CharacterSheetFactory.PP -> sheet?.copy(currentPP = max(0, min(sheet.maxPP, value)))
+ CharacterSheetFactory.PP -> sheet?.copy(currentPp = max(0, min(sheet.maxPP, value)))
else -> null
}
updated?.let {
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditFactory.kt
index 3040477..e79673a 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditFactory.kt
@@ -1,9 +1,16 @@
package com.pixelized.desktop.lwa.screen.characterSheet.edit
-import com.pixelized.desktop.lwa.business.DamageBonusUseCase.bonusDamage
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import com.pixelized.desktop.lwa.business.DamageBonusUseCase
import com.pixelized.desktop.lwa.business.SkillNormalizerUseCase.normalize
+import com.pixelized.desktop.lwa.parser.arithmetic.Arithmetic
+import com.pixelized.desktop.lwa.parser.arithmetic.ArithmeticParser
+import com.pixelized.desktop.lwa.parser.arithmetic.Instruction
import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet
-import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEditPageUio.SkillGroup
+import com.pixelized.desktop.lwa.screen.characterSheet.common.SkillFieldFactory
+import com.pixelized.desktop.lwa.screen.characterSheet.common.occupation
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.BaseSkillFieldUio
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.FieldUio
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__cha
@@ -13,86 +20,125 @@ import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__ch
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__int
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__pow
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__str
-import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__title
-import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__magic__add_action
-import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__magic__title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__name_placeholder
-import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__occupation__add_action
-import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__occupation__title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__acrobatics
-import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__add_action
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__aid
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__athletics
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__bargain
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__bonus_label
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__combat
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__discretion
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__dodge
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__empathy
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__grab
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__intimidation
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__level_label
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__magic_title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__perception
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__persuasion
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__search
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__sleight_of_hand
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__special_title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__spiel
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__throw
-import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__armor
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__damage_bonus
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__hit_point
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__movement
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__power_point
-import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__title
import org.jetbrains.compose.resources.getString
import java.util.UUID
import kotlin.math.ceil
import kotlin.math.max
+import kotlin.math.min
-class CharacterSheetEditFactory {
-
- fun convertToModel(sheet: CharacterSheetEditPageUio): CharacterSheet {
+class CharacterSheetEditFactory(
+ private val bonusDamageUseCase: DamageBonusUseCase,
+ private val skillFactory: SkillFieldFactory,
+ private val parser: ArithmeticParser,
+) {
+ fun updateCharacterSheet(
+ currentSheet: CharacterSheet?,
+ editedSheet: CharacterSheetEditPageUio,
+ ): CharacterSheet {
return CharacterSheet(
- id = sheet.id,
- name = sheet.name.value.value,
- strength = sheet.skills[0].fields[0].unpack(),
- dexterity = sheet.skills[0].fields[1].unpack(),
- constitution = sheet.skills[0].fields[2].unpack(),
- height = sheet.skills[0].fields[3].unpack(),
- intelligence = sheet.skills[0].fields[4].unpack(),
- power = sheet.skills[0].fields[5].unpack(),
- charisma = sheet.skills[0].fields[6].unpack(),
- movement = sheet.skills[1].fields[0].unpack(),
- currentHp = sheet.skills[1].fields[1].unpack(),
- maxHp = sheet.skills[1].fields[1].unpack(),
- currentPP = sheet.skills[1].fields[2].unpack(),
- maxPP = sheet.skills[1].fields[2].unpack(),
- damageBonus = sheet.skills[1].fields[3].unpack(),
- armor = sheet.skills[1].fields[4].unpack(),
- skills = sheet.skills[2].fields.map {
+ id = editedSheet.id,
+ name = editedSheet.name.value.value,
+ strength = editedSheet.strength.unpack(),
+ dexterity = editedSheet.dexterity.unpack(),
+ constitution = editedSheet.constitution.unpack(),
+ height = editedSheet.height.unpack(),
+ intelligence = editedSheet.intelligence.unpack(),
+ power = editedSheet.power.unpack(),
+ charisma = editedSheet.charisma.unpack(),
+ overrideMovement = editedSheet.movement.value.value.isNotBlank(),
+ movement = editedSheet.movement.unpack(),
+ overrideMaxHp = editedSheet.maxHp.value.value.isNotBlank(),
+ maxHp = editedSheet.maxHp.unpack(),
+ currentHp = editedSheet.maxHp.unpack().let {
+ max(0, min(it, currentSheet?.currentHp ?: it))
+ },
+ overrideMaxPP = editedSheet.maxPP.value.value.isNotBlank(),
+ maxPP = editedSheet.maxPP.unpack(),
+ currentPp = editedSheet.maxPP.unpack().let {
+ max(0, min(it, currentSheet?.currentPp ?: it))
+ },
+ overrideDamageBonus = editedSheet.damageBonus.value.value.isNotBlank(),
+ damageBonus = editedSheet.damageBonus.unpack(),
+ overrideArmor = editedSheet.armor.value.value.isNotBlank(),
+ armor = editedSheet.armor.unpack(),
+ commonSkills = editedSheet.commonSkills.map { editedSkill ->
+ val currentSkill = currentSheet?.commonSkills?.firstOrNull {
+ it.id == editedSkill.id
+ }
CharacterSheet.Skill(
- label = it.label.value,
- value = it.unpack(),
- used = false,
+ id = editedSkill.id,
+ label = editedSkill.label,
+ base = listOf(
+ Arithmetic(
+ sign = 1,
+ instruction = Instruction.Flat(editedSkill.base.value)
+ )
+ ),
+ bonus = editedSkill.bonus.value.value.toIntOrNull() ?: 0,
+ level = editedSkill.level.value.value.toIntOrNull() ?: 0,
+ occupation = editedSkill.option.checked.value,
+ used = currentSkill?.used ?: false,
)
},
- occupations = sheet.skills[3].fields.map {
+ specialSkills = editedSheet.specialSkills.map { editedSkill ->
+ val currentSkill = currentSheet?.specialSkills?.firstOrNull {
+ it.id == editedSkill.id
+ }
CharacterSheet.Skill(
- label = it.label.value,
- value = it.unpack(),
- used = false,
+ id = editedSkill.id,
+ label = editedSkill.label.value.value,
+ base = parser.parse(editedSkill.base.value.value),
+ bonus = editedSkill.bonus.value.value.toIntOrNull() ?: 0,
+ level = editedSkill.level.value.value.toIntOrNull() ?: 0,
+ occupation = editedSkill.options.occupation,
+ used = currentSkill?.used ?: false,
)
},
- magics = sheet.skills[4].fields.map {
+ magicSkills = editedSheet.magicSkills.map { editedSkill ->
+ val currentSkill = currentSheet?.magicSkills?.firstOrNull {
+ it.id == editedSkill.id
+ }
CharacterSheet.Skill(
- label = it.label.value,
- value = it.unpack(),
- used = false,
+ id = editedSkill.id,
+ label = editedSkill.label.value.value,
+ base = parser.parse(editedSkill.base.value.value),
+ bonus = editedSkill.bonus.value.value.toIntOrNull() ?: 0,
+ level = editedSkill.level.value.value.toIntOrNull() ?: 0,
+ occupation = editedSkill.options.occupation,
+ used = currentSkill?.used ?: false,
)
},
- rolls = sheet.rolls.map {
+ actions = editedSheet.actions.map {
CharacterSheet.Roll(
+ id = "", // TODO
label = it.label.value,
- roll = it.unpack(),
+ roll = parser.parse(value = it.unpack()),
)
},
)
@@ -100,41 +146,42 @@ class CharacterSheetEditFactory {
suspend fun convertToUio(
sheet: CharacterSheet?,
+ onDeleteSkill: (skillId: String) -> Unit,
): CharacterSheetEditPageUio {
val str = FieldUio.create(
initialLabel = getString(Res.string.character_sheet_edit__characteristics__str),
initialValue = sheet?.strength?.toString() ?: "",
- valuePlaceHolder = { "0" },
+ valuePlaceHolder = { "10" },
)
val dex = FieldUio.create(
initialLabel = getString(Res.string.character_sheet_edit__characteristics__dex),
initialValue = sheet?.dexterity?.toString() ?: "",
- valuePlaceHolder = { "0" }
+ valuePlaceHolder = { "11" }
)
val con = FieldUio.create(
initialLabel = getString(Res.string.character_sheet_edit__characteristics__con),
initialValue = sheet?.constitution?.toString() ?: "",
- valuePlaceHolder = { "0" }
+ valuePlaceHolder = { "15" }
)
val hei = FieldUio.create(
initialLabel = getString(Res.string.character_sheet_edit__characteristics__hei),
initialValue = sheet?.height?.toString() ?: "",
- valuePlaceHolder = { "0" }
+ valuePlaceHolder = { "13" }
)
val int = FieldUio.create(
initialLabel = getString(Res.string.character_sheet_edit__characteristics__int),
initialValue = sheet?.intelligence?.toString() ?: "",
- valuePlaceHolder = { "0" }
+ valuePlaceHolder = { "9" }
)
val pow = FieldUio.create(
initialLabel = getString(Res.string.character_sheet_edit__characteristics__pow),
initialValue = sheet?.power?.toString() ?: "",
- valuePlaceHolder = { "0" }
+ valuePlaceHolder = { "15" }
)
val cha = FieldUio.create(
initialLabel = getString(Res.string.character_sheet_edit__characteristics__cha),
initialValue = sheet?.charisma?.toString() ?: "",
- valuePlaceHolder = { "0" }
+ valuePlaceHolder = { "7" }
)
fun str(): Int = str.unpack() ?: 0
@@ -145,6 +192,9 @@ class CharacterSheetEditFactory {
fun pow(): Int = pow.unpack() ?: 0
fun cha(): Int = cha.unpack() ?: 0
+ val specialSkillsLabel = getString(Res.string.character_sheet_edit__skills__special_title)
+ val magicSkillsLabel = getString(Res.string.character_sheet_edit__skills__magic_title)
+
return CharacterSheetEditPageUio(
id = sheet?.id ?: UUID.randomUUID().toString(),
name = FieldUio.create(
@@ -152,153 +202,207 @@ class CharacterSheetEditFactory {
initialLabel = getString(Res.string.character_sheet_edit__name_placeholder),
initialValue = sheet?.name ?: ""
),
- skills = listOf(
- SkillGroup(
- type = SkillGroup.Type.CHARACTERISTICS,
- title = getString(Res.string.character_sheet_edit__characteristics__title),
- fields = listOf(str, dex, con, hei, int, pow, cha),
+ strength = str,
+ dexterity = dex,
+ constitution = con,
+ height = hei,
+ intelligence = int,
+ power = pow,
+ charisma = cha,
+ movement = FieldUio.create(
+ initialLabel = getString(Res.string.character_sheet_edit__sub_characteristics__movement),
+ initialValue = (if (sheet?.overrideMovement == true) "${sheet.movement}" else null)
+ ?: "",
+ valuePlaceHolder = { "10" }
+ ),
+ maxHp = FieldUio.create(
+ initialLabel = getString(Res.string.character_sheet_edit__sub_characteristics__hit_point),
+ initialValue = (if (sheet?.overrideMaxHp == true) "${sheet.maxHp}" else null) ?: "",
+ valuePlaceHolder = { "${ceil((con() + hei()) / 2f).toInt()}" }
+ ),
+ maxPP = FieldUio.create(
+ initialLabel = getString(Res.string.character_sheet_edit__sub_characteristics__power_point),
+ initialValue = (if (sheet?.overrideMaxPP == true) "${sheet.maxPP}" else null) ?: "",
+ valuePlaceHolder = { "${pow()}" }
+ ),
+ damageBonus = FieldUio.create(
+ initialLabel = getString(Res.string.character_sheet_edit__sub_characteristics__damage_bonus),
+ initialValue = (if (sheet?.overrideDamageBonus == true) sheet.damageBonus else null)
+ ?: "",
+ valuePlaceHolder = {
+ bonusDamageUseCase.bonusDamage(
+ strength = str(),
+ height = hei()
+ )
+ }
+ ),
+ armor = FieldUio.create(
+ initialLabel = getString(Res.string.character_sheet_edit__sub_characteristics__armor),
+ initialValue = (if (sheet?.overrideArmor == true) sheet.armor.toString() else null)
+ ?: "",
+ valuePlaceHolder = { "0" }
+ ),
+ commonSkills = listOf(
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.COMBAT_ID,
+ label = getString(Res.string.character_sheet_edit__skills__combat),
+ base = derivedStateOf { normalize(dex() * 2) },
),
- SkillGroup(
- type = SkillGroup.Type.SUB_CHARACTERISTICS,
- title = getString(Res.string.character_sheet_edit__sub_characteristics__title),
- fields = listOf(
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__sub_characteristics__movement),
- initialValue = sheet?.movement?.toString() ?: "",
- valuePlaceHolder = { "10" }
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__sub_characteristics__hit_point),
- initialValue = sheet?.maxHp?.toString() ?: "",
- valuePlaceHolder = { "${ceil((con() + hei()) / 2f).toInt()}" }
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__sub_characteristics__power_point),
- initialValue = sheet?.maxPP?.toString() ?: "",
- valuePlaceHolder = { "${pow()}" }
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__sub_characteristics__damage_bonus),
- initialValue = sheet?.damageBonus ?: "",
- valuePlaceHolder = { bonusDamage(strength = str(), height = hei()) }
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__sub_characteristics__armor),
- initialValue = sheet?.armor?.toString() ?: "",
- valuePlaceHolder = { "0" }
- ),
- ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.DODGE_ID,
+ label = getString(Res.string.character_sheet_edit__skills__dodge),
+ base = derivedStateOf { normalize(dex() * 2) },
),
- SkillGroup(
- type = SkillGroup.Type.SKILLS,
- title = getString(Res.string.character_sheet_edit__skills__title),
- action = getString(Res.string.character_sheet_edit__skills__add_action),
- fields = sheet?.skills?.map {
- FieldUio.create(
- initialLabel = it.label,
- initialValue = it.value.toString(),
- )
- } ?: listOf(
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__combat),
- valuePlaceHolder = { "${normalize(dex() * 2)}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__dodge),
- valuePlaceHolder = { "${normalize(dex() * 2)}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__grab),
- valuePlaceHolder = { "${normalize(str() + hei())}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__throw),
- valuePlaceHolder = { "${normalize(str() + dex())}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__athletics),
- valuePlaceHolder = { "${normalize(str() + con() * 2)}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__acrobatics),
- valuePlaceHolder = { "${normalize(dex() + con() * 2)}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__perception),
- valuePlaceHolder = { "${normalize(10 + int() * 2)}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__search),
- valuePlaceHolder = { "${normalize(10 + int() * 2)}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__empathy),
- valuePlaceHolder = { "${normalize(cha() + int())}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__persuasion),
- valuePlaceHolder = { "${normalize(cha() * 3)}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__intimidation),
- valuePlaceHolder = { "${normalize(cha() + max(pow(), hei()) * 2)}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__spiel),
- valuePlaceHolder = { "${normalize(cha() * 2 + int())}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__bargain),
- valuePlaceHolder = { "${normalize(cha() * 2)}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__discretion),
- valuePlaceHolder = { "${normalize(cha() + dex() * 2 - hei())}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__sleight_of_hand),
- valuePlaceHolder = { "${normalize(dex() * 2)}" },
- ),
- FieldUio.create(
- initialLabel = getString(Res.string.character_sheet_edit__skills__aid),
- valuePlaceHolder = { "${normalize(int() + dex())}" },
- ),
- ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.GRAB_ID,
+ label = getString(Res.string.character_sheet_edit__skills__grab),
+ base = derivedStateOf { normalize(str() + hei()) },
),
- SkillGroup(
- type = SkillGroup.Type.OCCUPATIONS,
- title = getString(Res.string.character_sheet_edit__occupation__title),
- action = getString(Res.string.character_sheet_edit__occupation__add_action),
- fields = sheet?.occupations?.map {
- FieldUio.create(
- initialLabel = it.label,
- initialValue = it.value.toString(),
- valuePlaceHolder = { "40" }
- )
- } ?: emptyList(),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.THROW_ID,
+ label = getString(Res.string.character_sheet_edit__skills__throw),
+ base = derivedStateOf { normalize(str() + dex()) },
),
- SkillGroup(
- type = SkillGroup.Type.MAGICS,
- title = getString(Res.string.character_sheet_edit__magic__title),
- action = getString(Res.string.character_sheet_edit__magic__add_action),
- fields = sheet?.magics?.map {
- FieldUio.create(
- initialLabel = it.label,
- initialValue = it.value.toString()
- )
- } ?: emptyList(),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.ATHLETICS_ID,
+ label = getString(Res.string.character_sheet_edit__skills__athletics),
+ base = derivedStateOf { normalize(str() + con() * 2) },
+ ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.ACROBATICS_ID,
+ label = getString(Res.string.character_sheet_edit__skills__acrobatics),
+ base = derivedStateOf { normalize(dex() + con() * 2) },
+ ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.PERCEPTION_ID,
+ label = getString(Res.string.character_sheet_edit__skills__perception),
+ base = derivedStateOf { normalize(10 + int() * 2) },
+ ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.SEARCH_ID,
+ label = getString(Res.string.character_sheet_edit__skills__search),
+ base = derivedStateOf { normalize(10 + int() * 2) },
+ ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.EMPATHY_ID,
+ label = getString(Res.string.character_sheet_edit__skills__empathy),
+ base = derivedStateOf { normalize(cha() + int()) },
+ ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.PERSUASION_ID,
+ label = getString(Res.string.character_sheet_edit__skills__persuasion),
+ base = derivedStateOf { normalize(cha() * 3) },
+ ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.INTIMIDATION_ID,
+ label = getString(Res.string.character_sheet_edit__skills__intimidation),
+ base = derivedStateOf { normalize(cha() + max(pow(), hei()) * 2) },
+ ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.SPIEL_ID,
+ label = getString(Res.string.character_sheet_edit__skills__spiel),
+ base = derivedStateOf { normalize(cha() * 2 + int()) },
+ ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.BARGAIN_ID,
+ label = getString(Res.string.character_sheet_edit__skills__bargain),
+ base = derivedStateOf { normalize(cha() * 2) },
+ ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.DISCRETION_ID,
+ label = getString(Res.string.character_sheet_edit__skills__discretion),
+ base = derivedStateOf { normalize(cha() + dex() * 2 - hei()) },
+ ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.SLEIGHT_OF_HAND_ID,
+ label = getString(Res.string.character_sheet_edit__skills__sleight_of_hand),
+ base = derivedStateOf { normalize(dex() * 2) },
+ ),
+ createBaseSkill(
+ sheet = sheet,
+ id = CharacterSheet.CommonSkillId.AID_ID,
+ label = getString(Res.string.character_sheet_edit__skills__aid),
+ base = derivedStateOf { normalize(int() + dex()) },
),
),
- rolls = sheet?.rolls?.map {
- FieldUio.create(
- isLabelEditable = true,
- initialLabel = it.label,
- initialValue = it.roll,
+ specialSkills = sheet?.specialSkills?.map { skill ->
+ skillFactory.createSkill(
+ id = skill.id,
+ label = specialSkillsLabel,
+ labelValue = skill.label,
+ baseValue = parser.convertInstructionToString(instructions = skill.base),
+ bonusValue = skill.bonus.takeIf { it > 0 }?.toString() ?: "",
+ levelValue = skill.level.takeIf { it > 0 }?.toString() ?: "",
+ options = run {
+ val current = sheet.specialSkills.firstOrNull { it.id == skill.id }
+ listOf(
+ skillFactory.occupationOption(checked = current?.occupation ?: false),
+ skillFactory.deleteOption { onDeleteSkill(skill.id) },
+ )
+ },
)
- } ?: emptyList()
+ } ?: emptyList(),
+ magicSkills = sheet?.magicSkills?.map { skill ->
+ skillFactory.createSkill(
+ id = skill.id,
+ label = magicSkillsLabel,
+ labelValue = skill.label,
+ baseValue = parser.convertInstructionToString(instructions = skill.base),
+ bonusValue = skill.bonus.takeIf { it > 0 }?.toString() ?: "",
+ levelValue = skill.level.takeIf { it > 0 }?.toString() ?: "",
+ options = run {
+ val current = sheet.magicSkills.firstOrNull { it.id == skill.id }
+ listOf(
+ skillFactory.occupationOption(checked = current?.occupation ?: false),
+ skillFactory.deleteOption { onDeleteSkill(skill.id) },
+ )
+ },
+ )
+ } ?: emptyList(),
+ actions = emptyList(),
)
}
+ private suspend fun createBaseSkill(
+ sheet: CharacterSheet?,
+ id: String,
+ label: String,
+ base: State,
+ ): BaseSkillFieldUio {
+ val skill = sheet?.commonSkills?.firstOrNull { it.id == id }
+
+ return BaseSkillFieldUio(
+ id = id,
+ label = label,
+ base = base,
+ bonus = skillFactory.createWrapper(
+ label = getString(Res.string.character_sheet_edit__skills__bonus_label),
+ value = skill?.bonus?.takeIf { it > 0 }?.toString() ?: "",
+ ),
+ level = skillFactory.createWrapper(
+ label = getString(Res.string.character_sheet_edit__skills__level_label),
+ value = skill?.level?.takeIf { it > 0 }?.toString() ?: "",
+ ),
+ option = skillFactory.occupationOption(skill?.occupation ?: false),
+ )
+ }
+
+
private inline fun FieldUio.unpack(): T {
val tmp = value.value.ifBlank { valuePlaceHolder.value }
return when (T::class) {
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt
index 9a7b05c..5febf95 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt
@@ -21,6 +21,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -32,44 +33,70 @@ import com.pixelized.desktop.lwa.LocalWindowController
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.navigation.screen.LocalScreenController
import com.pixelized.desktop.lwa.navigation.window.LocalWindow
+import com.pixelized.desktop.lwa.navigation.window.destination.CharacterSheetCreateWindow
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.BaseSkillFieldUio
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.BaseSkillForm
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.FieldUio
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.Form
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.SkillFieldUio
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.SkillForm
import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__common_title
+import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__magic_title
+import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__special_title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__add_roll_action
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__characteristics__title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__save_action
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__magic_action
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__special_action
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__title
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
-import org.koin.core.annotation.KoinExperimentalAPI
@Stable
data class CharacterSheetEditPageUio(
val id: String,
val name: FieldUio,
- val skills: List,
- val rolls: List,
+ val strength: FieldUio,
+ val dexterity: FieldUio,
+ val constitution: FieldUio,
+ val height: FieldUio,
+ val intelligence: FieldUio,
+ val power: FieldUio,
+ val charisma: FieldUio,
+ val movement: FieldUio,
+ val maxHp: FieldUio,
+ val maxPP: FieldUio,
+ val damageBonus: FieldUio,
+ val armor: FieldUio,
+ val commonSkills: List,
+ val specialSkills: List,
+ val magicSkills: List,
+ val actions: List,
) {
- @Stable
- data class SkillGroup(
- val title: String,
- val type: Type,
- val action: String? = null,
- val fields: List,
- ) {
- @Stable
- enum class Type {
- CHARACTERISTICS,
- SUB_CHARACTERISTICS,
- SKILLS,
- OCCUPATIONS,
- MAGICS,
- OTHERS,
- }
- }
+ val characteristics
+ get() = listOf(
+ strength,
+ dexterity,
+ constitution,
+ height,
+ intelligence,
+ power,
+ charisma,
+ )
+
+ val subCharacteristics
+ get() = listOf(
+ movement,
+ maxHp,
+ maxPP,
+ damageBonus,
+ armor,
+ )
}
-@OptIn(KoinExperimentalAPI::class)
@Composable
fun CharacterSheetEditPage(
viewModel: CharacterSheetEditViewModel = koinViewModel(),
@@ -79,6 +106,10 @@ fun CharacterSheetEditPage(
val screen = LocalScreenController.current
val scope = rememberCoroutineScope()
+ LaunchedEffect(Unit) {
+ window.resize(size = CharacterSheetCreateWindow.size)
+ }
+
Surface(
modifier = Modifier.fillMaxSize(),
) {
@@ -91,8 +122,21 @@ fun CharacterSheetEditPage(
null
}
},
- onNewSkill = viewModel::onSkill,
- onNewCategory = viewModel::onNewRoll,
+ onNewSpecialSkill = {
+ scope.launch {
+ viewModel.onNewSpecialSkill()
+ }
+ },
+ onNewMagicSkill = {
+ scope.launch {
+ viewModel.onNewMagicSkill()
+ }
+ },
+ onNewAction = {
+ scope.launch {
+ viewModel.onNewAction()
+ }
+ },
onSave = {
scope.launch {
viewModel.save()
@@ -109,8 +153,9 @@ fun CharacterSheetEditPage(
fun CharacterSheetEdit(
form: CharacterSheetEditPageUio,
onBack: (() -> Unit)?,
- onNewSkill: (CharacterSheetEditPageUio.SkillGroup) -> Unit,
- onNewCategory: () -> Unit,
+ onNewSpecialSkill: () -> Unit,
+ onNewMagicSkill: () -> Unit,
+ onNewAction: () -> Unit,
onSave: () -> Unit,
) {
Scaffold(
@@ -142,7 +187,7 @@ fun CharacterSheetEdit(
modifier = Modifier
.verticalScroll(state = rememberScrollState())
.padding(paddingValues = paddingValues)
- .padding(all = 24.dp),
+ .padding(all = 16.dp),
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
Form(
@@ -150,56 +195,161 @@ fun CharacterSheetEdit(
field = form.name,
)
- form.skills.forEach {
- DecoratedBox(
- modifier = Modifier.animateContentSize(),
+ DecoratedBox(
+ modifier = Modifier.animateContentSize(),
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- Text(
- modifier = Modifier.padding(vertical = 8.dp),
- style = MaterialTheme.typography.caption,
- text = it.title,
+ Text(
+ modifier = Modifier.padding(vertical = 8.dp),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ style = MaterialTheme.typography.caption,
+ text = stringResource(Res.string.character_sheet_edit__characteristics__title),
+ )
+ form.characteristics.forEach {
+ Form(
+ modifier = Modifier.fillMaxWidth(),
+ field = it,
)
- it.fields.forEach {
- Form(
- modifier = Modifier.fillMaxWidth(),
- field = it,
+ }
+ }
+ }
+
+ DecoratedBox(
+ modifier = Modifier.animateContentSize(),
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ modifier = Modifier.padding(vertical = 8.dp),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ style = MaterialTheme.typography.caption,
+ text = stringResource(Res.string.character_sheet_edit__sub_characteristics__title),
+ )
+ form.subCharacteristics.forEach {
+ Form(
+ modifier = Modifier.fillMaxWidth(),
+ field = it,
+ )
+ }
+ }
+ }
+
+ DecoratedBox(
+ modifier = Modifier.animateContentSize(),
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ modifier = Modifier.padding(vertical = 8.dp),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ style = MaterialTheme.typography.caption,
+ text = stringResource(Res.string.character_sheet__skills__common_title),
+ )
+ form.commonSkills.forEach {
+ BaseSkillForm(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(end = 4.dp),
+ field = it,
+ )
+ }
+ }
+ }
+
+ DecoratedBox(
+ modifier = Modifier.animateContentSize(),
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ modifier = Modifier.padding(vertical = 8.dp),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ style = MaterialTheme.typography.caption,
+ text = stringResource(Res.string.character_sheet__skills__special_title),
+ )
+ form.specialSkills.forEach {
+ SkillForm(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(end = 4.dp),
+ field = it,
+ )
+ }
+ TextButton(
+ modifier = Modifier.align(alignment = Alignment.End),
+ onClick = onNewSpecialSkill,
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
+ ) {
+ Text(
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = stringResource(Res.string.character_sheet_edit__skills__special_action),
+ )
+ Icon(
+ imageVector = Icons.Default.Add,
+ contentDescription = null,
)
- }
- it.action?.let { label ->
- Row(
- modifier = Modifier.fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(
- space = 8.dp,
- alignment = Alignment.End
- )
- ) {
- TextButton(
- onClick = { onNewSkill(it) },
- ) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
- ) {
- Text(
- text = label,
- )
- Icon(
- imageVector = Icons.Default.Add,
- contentDescription = null,
- )
- }
- }
- }
}
}
}
}
- form.rolls.forEach {
+ DecoratedBox(
+ modifier = Modifier.animateContentSize(),
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ modifier = Modifier.padding(vertical = 8.dp),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ style = MaterialTheme.typography.caption,
+ text = stringResource(Res.string.character_sheet__skills__magic_title),
+ )
+ form.magicSkills.forEach {
+ SkillForm(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(end = 4.dp),
+ field = it,
+ )
+ }
+ TextButton(
+ modifier = Modifier.align(alignment = Alignment.End),
+ onClick = onNewMagicSkill,
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
+ ) {
+ Text(
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = stringResource(Res.string.character_sheet_edit__skills__magic_action),
+ )
+ Icon(
+ imageVector = Icons.Default.Add,
+ contentDescription = null,
+ )
+ }
+ }
+ }
+ }
+
+ form.actions.forEach {
Form(
modifier = Modifier.fillMaxWidth(),
valueWidth = 120.dp,
@@ -212,9 +362,13 @@ fun CharacterSheetEdit(
horizontalArrangement = Arrangement.End,
) {
TextButton(
- onClick = onNewCategory,
+ onClick = onNewAction,
) {
- Text(text = stringResource(Res.string.character_sheet_edit__add_roll_action))
+ Text(
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = stringResource(Res.string.character_sheet_edit__add_roll_action)
+ )
}
}
@@ -225,7 +379,11 @@ fun CharacterSheetEdit(
TextButton(
onClick = onSave,
) {
- Text(text = stringResource(Res.string.character_sheet_edit__save_action))
+ Text(
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = stringResource(Res.string.character_sheet_edit__save_action),
+ )
}
}
}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditViewModel.kt
index 359d3bc..87029cc 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditViewModel.kt
@@ -6,78 +6,106 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import com.pixelized.desktop.lwa.navigation.screen.destination.CharacterSheetEditDestination
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
-import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEditPageUio.SkillGroup
+import com.pixelized.desktop.lwa.screen.characterSheet.common.SkillFieldFactory
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.FieldUio
import kotlinx.coroutines.runBlocking
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__magic_title
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__special_title
+import org.jetbrains.compose.resources.getString
+import java.util.UUID
class CharacterSheetEditViewModel(
private val characterSheetRepository: CharacterSheetRepository,
+ private val sheetFactory: CharacterSheetEditFactory,
+ private val skillFactory: SkillFieldFactory,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val argument = CharacterSheetEditDestination.Argument(savedStateHandle)
- private val factory = CharacterSheetEditFactory()
val enableBack = argument.enableBack
private val _characterSheet = mutableStateOf(
characterSheetRepository.characterSheetFlow(id = argument.id).value.let {
- runBlocking { factory.convertToUio(it) }
+ runBlocking {
+ sheetFactory.convertToUio(
+ sheet = it,
+ onDeleteSkill = ::deleteSkill,
+ )
+ }
}
)
val characterSheet: State get() = _characterSheet
- fun onSkill(skill: SkillGroup) {
- val sheet = _characterSheet.value
- _characterSheet.value = sheet.copy(
- skills = sheet.skills.map { group ->
- if (skill.title == group.title) {
- group.copy(
- fields = mutableListOf().apply {
- addAll(group.fields)
- add(
- FieldUio.create(
- isLabelEditable = true,
- initialLabel = "",
- valuePlaceHolder = {
- when (group.type) {
- SkillGroup.Type.CHARACTERISTICS -> ""
- SkillGroup.Type.SUB_CHARACTERISTICS -> ""
- SkillGroup.Type.SKILLS -> "0"
- SkillGroup.Type.OCCUPATIONS -> "40"
- SkillGroup.Type.MAGICS -> "0"
- SkillGroup.Type.OTHERS -> ""
- }
- },
- )
- )
- }
- )
- } else {
- group
- }
- }
+ suspend fun onNewSpecialSkill() {
+ val id = UUID.randomUUID().toString()
+ val skill = skillFactory.createSkill(
+ id = id,
+ label = getString(Res.string.character_sheet_edit__skills__special_title),
+ options = listOf(
+ skillFactory.occupationOption(checked = false),
+ skillFactory.deleteOption { deleteSkill(skillId = id) }
+ ),
+ )
+ val skills = _characterSheet.value.specialSkills
+ .toMutableList()
+ .also { it.add(skill) }
+ _characterSheet.value = characterSheet.value.copy(
+ specialSkills = skills,
)
}
- fun onNewRoll() {
- val sheet = _characterSheet.value
- _characterSheet.value = sheet.copy(
- rolls = sheet.rolls.toMutableList().apply {
- add(
- FieldUio.create(
- initialLabel = "",
- isLabelEditable = true,
- valuePlaceHolder = { "" },
- )
- )
- }
+ suspend fun onNewMagicSkill() {
+ val id = UUID.randomUUID().toString()
+ val skill = skillFactory.createSkill(
+ id = id,
+ label = getString(Res.string.character_sheet_edit__skills__magic_title),
+ options = listOf(
+ skillFactory.occupationOption(checked = false),
+ skillFactory.deleteOption { deleteSkill(skillId = id) }
+ ),
+ )
+ val skills = _characterSheet.value.magicSkills
+ .toMutableList()
+ .also { it.add(skill) }
+ _characterSheet.value = characterSheet.value.copy(
+ magicSkills = skills,
+ )
+ }
+
+ suspend fun onNewAction() {
+ val field = FieldUio.create(
+ initialLabel = "",
+ isLabelEditable = true,
+ valuePlaceHolder = { "" },
+ )
+ val actions = _characterSheet.value.actions.toMutableList().also {
+ it.add(field)
+ }
+ _characterSheet.value = _characterSheet.value.copy(
+ actions = actions,
+ )
+ }
+
+ private fun deleteSkill(skillId: String) {
+ _characterSheet.value = _characterSheet.value.copy(
+ specialSkills = _characterSheet.value.specialSkills.toMutableList().also { skills ->
+ skills.removeIf { it.id == skillId }
+ },
+ magicSkills = _characterSheet.value.magicSkills.toMutableList().also { skills ->
+ skills.removeIf { it.id == skillId }
+ },
)
}
suspend fun save() {
- val sheet = _characterSheet.value
- val model = factory.convertToModel(sheet = sheet)
- characterSheetRepository.save(characterSheet = model)
+ val updatedSheet = sheetFactory.updateCharacterSheet(
+ currentSheet = characterSheetRepository.characterSheetFlow(id = _characterSheet.value.id).value,
+ editedSheet = _characterSheet.value,
+ )
+ characterSheetRepository.save(
+ characterSheet = updatedSheet,
+ )
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/BaseSkillField.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/BaseSkillField.kt
new file mode 100644
index 0000000..748c69e
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/BaseSkillField.kt
@@ -0,0 +1,117 @@
+package com.pixelized.desktop.lwa.screen.characterSheet.edit.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.ContentAlpha
+import androidx.compose.material.DropdownMenu
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.MoreVert
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.option.CheckedOption
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.option.DropDownCheckedMenuItem
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.textfield.TextFieldWrapper
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.textfield.TextFieldWrapperUio
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__base_label
+import org.jetbrains.compose.resources.stringResource
+
+@Stable
+class BaseSkillFieldUio(
+ val id: String,
+ val label: String,
+ val base: State,
+ val bonus: TextFieldWrapperUio,
+ val level: TextFieldWrapperUio,
+ val option: CheckedOption,
+)
+
+@Composable
+fun BaseSkillForm(
+ modifier: Modifier = Modifier,
+ field: BaseSkillFieldUio,
+) {
+ val showMenu = remember { mutableStateOf(false) }
+
+ Row(
+ modifier = modifier,
+ horizontalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.End),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ modifier = Modifier
+ .weight(1f)
+ .padding(start = 16.dp, bottom = 12.dp)
+ .align(alignment = Alignment.Bottom),
+ style = MaterialTheme.typography.body1,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = field.label,
+ )
+ Column(
+ modifier = Modifier
+ .width(width = 96.dp)
+ .padding(start = 16.dp),
+ ) {
+ Text(
+ style = MaterialTheme.typography.caption,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ color = MaterialTheme.colors.onSurface.copy(ContentAlpha.medium),
+ text = stringResource(Res.string.character_sheet_edit__skills__base_label),
+ )
+ Text(
+ style = MaterialTheme.typography.body1,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = "+${field.base.value}",
+ )
+ }
+ TextFieldWrapper(
+ modifier = Modifier.width(width = 96.dp),
+ wrapper = field.bonus,
+ )
+ TextFieldWrapper(
+ modifier = Modifier.width(width = 96.dp),
+ wrapper = field.level,
+ )
+ Box {
+ IconButton(
+ onClick = { showMenu.value = showMenu.value.not() },
+ ) {
+ Icon(
+ imageVector = Icons.Default.MoreVert,
+ tint = MaterialTheme.colors.primary,
+ contentDescription = null,
+ )
+ }
+ DropdownMenu(
+ expanded = showMenu.value,
+ onDismissRequest = { showMenu.value = false }
+ ) {
+ DropDownCheckedMenuItem(
+ wrapper = field.option,
+ onClick = {
+ showMenu.value = false
+ field.option.onOption()
+ }
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/FieldUio.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/Field.kt
similarity index 88%
rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/FieldUio.kt
rename to composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/Field.kt
index cfe360c..fdaf53e 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/FieldUio.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/Field.kt
@@ -4,7 +4,9 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
+import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
@@ -24,7 +26,9 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.pixelized.desktop.lwa.utils.preview.ContentPreview
+@Deprecated("redo")
@Stable
open class FieldUio(
val isLabelDisplayed: Boolean,
@@ -127,4 +131,23 @@ fun Form(
}
}
}
+}
+
+@Composable
+@Preview
+private fun Preview() {
+ ContentPreview(
+ paddingValues = PaddingValues(all = 16.dp)
+ ) {
+ Form(
+ field = FieldUio.create(
+ isLabelDisplayed = true,
+ isLabelEditable = true,
+ initialLabel = "label",
+ labelPlaceHolder = { "labelPlaceholder" },
+ initialValue = "value",
+ valuePlaceHolder = { "valuePlaceholder" },
+ )
+ )
+ }
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/SpecialSkillField.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/SpecialSkillField.kt
new file mode 100644
index 0000000..9dbc3fa
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/SpecialSkillField.kt
@@ -0,0 +1,257 @@
+package com.pixelized.desktop.lwa.screen.characterSheet.edit.composable
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.DropdownMenu
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.MoreVert
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.singleWindowApplication
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.textfield.TextFieldWrapperUio
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.textfield.TextFieldWrapper
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.option.ActionOption
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.option.CheckedOption
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.option.DropDownMenuItemWrapper
+import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.option.OptionUio
+import com.pixelized.desktop.lwa.utils.preview.ContentPreview
+import java.util.UUID
+
+@Stable
+class SkillFieldUio(
+ val id: String,
+ val label: TextFieldWrapperUio,
+ val base: TextFieldWrapperUio,
+ val bonus: TextFieldWrapperUio,
+ val level: TextFieldWrapperUio,
+ val options: List,
+)
+
+@Composable
+fun SkillForm(
+ modifier: Modifier = Modifier,
+ field: SkillFieldUio,
+) {
+ val showMenu = remember { mutableStateOf(false) }
+
+ Row(
+ modifier = modifier,
+ horizontalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.End),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ TextFieldWrapper(
+ modifier = Modifier.weight(1f),
+ wrapper = field.label,
+ )
+ TextFieldWrapper(
+ modifier = Modifier.width(width = 96.dp),
+ wrapper = field.base,
+ )
+ TextFieldWrapper(
+ modifier = Modifier.width(width = 96.dp),
+ wrapper = field.bonus,
+ )
+ TextFieldWrapper(
+ modifier = Modifier.width(width = 96.dp),
+ wrapper = field.level,
+ )
+ if (field.options.isNotEmpty()) {
+ Box {
+ IconButton(
+ onClick = { showMenu.value = showMenu.value.not() },
+ ) {
+ Icon(
+ imageVector = Icons.Default.MoreVert,
+ tint = MaterialTheme.colors.primary,
+ contentDescription = null,
+ )
+ }
+ DropdownMenu(
+ expanded = showMenu.value,
+ onDismissRequest = { showMenu.value = false }
+ ) {
+ field.options.forEach {
+ DropDownMenuItemWrapper(
+ wrapper = it,
+ onClick = {
+ showMenu.value = false
+ it.onOption()
+ }
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+// PREVIEW
+
+@Composable
+@Preview
+private fun Preview() {
+ ContentPreview(
+ modifier = Modifier.width(width = 1000.dp),
+ paddingValues = PaddingValues(all = 16.dp),
+ ) {
+ SkillForm(
+ field = skillFieldUio(
+ labelLabel = "compétence",
+ labelValue = "Bagarre",
+ baseLabel = "Base",
+ baseValue = "Stat(DEX*2)",
+ bonusLabel = "Bonus",
+ bonusValue = "50",
+ levelLabel = "Niveau",
+ levelValue = "5",
+ ),
+ )
+ }
+}
+
+fun main() = singleWindowApplication(title = "Context menu") {
+ ContentPreview(
+ modifier = Modifier.width(1000.dp),
+ paddingValues = PaddingValues(all = 16.dp),
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(space = 8.dp)
+ ) {
+ SkillForm(
+ field = skillFieldUio(
+ labelLabel = "compétence",
+ labelValue = "Bagarre",
+ baseLabel = "Base",
+ baseValue = "Stat(DEX*2)",
+ bonusLabel = "Bonus",
+ bonusValue = "55",
+ levelLabel = "Niveau",
+ levelValue = "0",
+ options = listOf(
+ CheckedOption.OccupationOption(
+ label = "Base minimum 40",
+ checked = mutableStateOf(true),
+ onOption = { },
+ ),
+ ActionOption.DeleteOptionUio(
+ icon = Icons.Default.Delete,
+ label = "Supprimer",
+ onOption = { },
+ ),
+ )
+ ),
+ )
+ SkillForm(
+ field = skillFieldUio(
+ labelLabel = "compétence",
+ labelValue = "Esquive",
+ baseLabel = "Base",
+ baseValue = "Stat(DEX*2)",
+ bonusLabel = "Bonus",
+ bonusValue = "40",
+ levelLabel = "Niveau",
+ levelValue = "0",
+ options = listOf(
+ CheckedOption.OccupationOption(
+ label = "Base minimum 40",
+ checked = mutableStateOf(true),
+ onOption = { },
+ ),
+ ActionOption.DeleteOptionUio(
+ icon = Icons.Default.Delete,
+ label = "Supprimer",
+ onOption = { },
+ ),
+ )
+ ),
+ )
+ SkillForm(
+ field = skillFieldUio(
+ labelLabel = "compétence",
+ labelValue = "Saisie",
+ baseLabel = "Base",
+ baseValue = "Stat(FOR+TAI)",
+ bonusLabel = "Bonus",
+ bonusValue = "0",
+ levelLabel = "Niveau",
+ levelValue = "0",
+ options = listOf(
+ CheckedOption.OccupationOption(
+ label = "Base minimum 40",
+ checked = mutableStateOf(true),
+ onOption = { },
+ ),
+ ActionOption.DeleteOptionUio(
+ icon = Icons.Default.Delete,
+ label = "Supprimer",
+ onOption = { },
+ ),
+ )
+ ),
+ )
+ }
+ }
+}
+
+private fun skillFieldUio(
+ labelLabel: String,
+ labelValue: String,
+ baseLabel: String,
+ baseValue: String,
+ bonusLabel: String,
+ bonusValue: String,
+ levelLabel: String,
+ levelValue: String,
+ options: List = emptyList(),
+): SkillFieldUio {
+ return SkillFieldUio(
+ id = UUID.randomUUID().toString(),
+ label = mutableStateOf(labelValue).let { state ->
+ TextFieldWrapperUio(
+ enable = true,
+ label = labelLabel,
+ value = state,
+ onValueChange = { state.value = it },
+ )
+ },
+ base = mutableStateOf(baseValue).let { state ->
+ TextFieldWrapperUio(
+ enable = true,
+ label = baseLabel,
+ value = state,
+ onValueChange = { state.value = it },
+ )
+ },
+ bonus = mutableStateOf(bonusValue).let { state ->
+ TextFieldWrapperUio(
+ enable = true,
+ label = bonusLabel,
+ value = state,
+ onValueChange = { state.value = it },
+ )
+ },
+ level = mutableStateOf(levelValue).let { state ->
+ TextFieldWrapperUio(
+ enable = true,
+ label = levelLabel,
+ value = state,
+ onValueChange = { state.value = it },
+ )
+ },
+ options = options,
+ )
+}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/option/ActionOption.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/option/ActionOption.kt
new file mode 100644
index 0000000..7b0383c
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/option/ActionOption.kt
@@ -0,0 +1,45 @@
+package com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.option
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.dp
+
+@Stable
+sealed class ActionOption(
+ val icon: ImageVector,
+ val label: String,
+ onOption: () -> Unit,
+) : OptionUio(onOption = onOption) {
+
+ class DeleteOptionUio(icon: ImageVector, label: String, onOption: () -> Unit) :
+ ActionOption(icon = icon, label = label, onOption = onOption)
+}
+
+@Composable
+fun DropDownActionMenuItem(
+ modifier: Modifier = Modifier,
+ wrapper: ActionOption,
+ onClick: () -> Unit,
+) {
+ DropdownMenuItem(
+ modifier = modifier,
+ onClick = onClick,
+ ) {
+ Icon(
+ tint = MaterialTheme.colors.primary,
+ imageVector = wrapper.icon,
+ contentDescription = null,
+ )
+ Text(
+ modifier = Modifier.padding(start = 8.dp),
+ text = wrapper.label,
+ )
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/option/CheckedOption.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/option/CheckedOption.kt
new file mode 100644
index 0000000..55c6dbd
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/option/CheckedOption.kt
@@ -0,0 +1,49 @@
+package com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.option
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Checkbox
+import androidx.compose.material.CheckboxDefaults
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Stable
+sealed class CheckedOption(
+ val label: String,
+ val checked: State,
+ onOption: () -> Unit,
+) : OptionUio(onOption = onOption) {
+ class OccupationOption(label: String, checked: State, onOption: () -> Unit) :
+ CheckedOption(label = label, checked = checked, onOption = onOption)
+}
+
+@Composable
+fun DropDownCheckedMenuItem(
+ modifier: Modifier = Modifier,
+ wrapper: CheckedOption,
+ onClick: () -> Unit,
+) {
+ DropdownMenuItem(
+ modifier = modifier,
+ onClick = onClick,
+ ) {
+ Checkbox(
+ modifier = Modifier.size(size = 24.dp),
+ checked = wrapper.checked.value,
+ colors = CheckboxDefaults.colors(
+ checkedColor = MaterialTheme.colors.primary,
+ ),
+ onCheckedChange = null
+ )
+ Text(
+ modifier = Modifier.padding(start = 8.dp),
+ text = wrapper.label,
+ )
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/option/Option.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/option/Option.kt
new file mode 100644
index 0000000..2c66b12
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/option/Option.kt
@@ -0,0 +1,31 @@
+package com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.option
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.Modifier
+
+@Stable
+sealed class OptionUio(
+ val onOption: () -> Unit,
+)
+
+@Composable
+fun DropDownMenuItemWrapper(
+ modifier: Modifier = Modifier,
+ wrapper: OptionUio,
+ onClick: () -> Unit,
+) {
+ when (wrapper) {
+ is ActionOption -> DropDownActionMenuItem(
+ modifier = modifier,
+ wrapper = wrapper,
+ onClick = onClick,
+ )
+
+ is CheckedOption -> DropDownCheckedMenuItem(
+ modifier = modifier,
+ wrapper = wrapper,
+ onClick = onClick,
+ )
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/textfield/FormWrapper.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/textfield/FormWrapper.kt
new file mode 100644
index 0000000..f7cd5be
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/textfield/FormWrapper.kt
@@ -0,0 +1,51 @@
+package com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.textfield
+
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.material.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.text.style.TextOverflow
+import com.pixelized.desktop.lwa.utils.rememberKeyboardActions
+
+@Stable
+data class TextFieldWrapperUio(
+ val enable: Boolean,
+ val label: String,
+ val value: State,
+ val onValueChange: (String) -> Unit,
+)
+
+@Composable
+fun TextFieldWrapper(
+ modifier: Modifier = Modifier,
+ wrapper: TextFieldWrapperUio,
+) {
+ val focus = LocalFocusManager.current
+
+ TextField(
+ modifier = modifier,
+ colors = TextFieldDefaults.textFieldColors(
+ backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.03f),
+ ),
+ keyboardActions = rememberKeyboardActions {
+ focus.moveFocus(FocusDirection.Next)
+ },
+ enabled = wrapper.enable,
+ singleLine = true,
+ label = {
+ Text(
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = wrapper.label
+ )
+ },
+ onValueChange = { wrapper.onValueChange(it) },
+ value = wrapper.value.value,
+ )
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt
index 99a7a72..f5f4c09 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt
@@ -31,7 +31,6 @@ import lwacharactersheet.composeapp.generated.resources.main_page__network_actio
import lwacharactersheet.composeapp.generated.resources.main_page__roll_history_action
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
-import org.koin.core.annotation.KoinExperimentalAPI
@Stable
data class CharacterUio(
@@ -39,7 +38,6 @@ data class CharacterUio(
val name: String,
)
-@OptIn(KoinExperimentalAPI::class)
@Composable
fun MainPage(
viewModel: MainPageViewModel = koinViewModel(),
@@ -54,7 +52,7 @@ fun MainPage(
modifier = Modifier
.verticalScroll(state = rememberScrollState())
.fillMaxSize()
- .padding(horizontal = 24.dp),
+ .padding(horizontal = 16.dp),
contentAlignment = Alignment.Center,
) {
MainPageContent(
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollPage.kt
index 69fced2..038957d 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollPage.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollPage.kt
@@ -44,7 +44,6 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
-import androidx.lifecycle.viewmodel.compose.viewModel
import com.pixelized.desktop.lwa.screen.roll.DifficultyUio.Difficulty
import com.pixelized.desktop.lwa.utils.DisableInteractionSource
import kotlinx.coroutines.launch
@@ -60,7 +59,6 @@ import lwacharactersheet.composeapp.generated.resources.roll_page__roll__success
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
-import org.koin.core.annotation.KoinExperimentalAPI
@Stable
data class RollTitleUio(
@@ -84,7 +82,6 @@ data class DifficultyUio(
}
}
-@OptIn(KoinExperimentalAPI::class)
@Composable
fun RollPage(
viewModel: RollViewModel = koinViewModel(),
@@ -131,6 +128,10 @@ fun RollPage(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
+ modifier = Modifier.graphicsLayer {
+ this.scaleX = viewModel.rollScale.value
+ this.scaleY = viewModel.rollScale.value
+ },
contentAlignment = Alignment.Center
) {
Icon(
@@ -150,8 +151,8 @@ fun RollPage(
AnimatedContent(
targetState = viewModel.rollResult.value?.value?.toString() ?: "",
transitionSpec = {
- val enter = fadeIn() + slideInVertically { 32 }
- val exit = fadeOut() + slideOutVertically { -32 }
+ val enter = fadeIn() + slideInVertically { -32 }
+ val exit = fadeOut() + slideOutVertically { 32 }
enter togetherWith exit using SizeTransform(clip = false)
},
) { label ->
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt
index 31136a1..45d3314 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt
@@ -3,6 +3,7 @@ package com.pixelized.desktop.lwa.screen.roll
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
@@ -48,6 +49,7 @@ class RollViewModel(
val rollResult: State get() = _rollResult
val rollRotation = Animatable(0f)
+ val rollScale = Animatable(1f)
private val _rollDifficulty = mutableStateOf(null)
val rollDifficulty: State get() = _rollDifficulty
@@ -95,7 +97,10 @@ class RollViewModel(
rollAction: String,
rollSuccessValue: Int?,
) {
- runBlocking { rollRotation.snapTo(0f) }
+ runBlocking {
+ rollRotation.snapTo(0f)
+ rollScale.snapTo(1f)
+ }
this.sheet = characterSheetRepository.characterSheetFlow(id = sheet.id).value!!
this.rollAction = rollAction
@@ -124,6 +129,22 @@ class RollViewModel(
rollJob?.cancel()
rollJob = launch {
+ launch {
+ rollScale.animateTo(
+ targetValue = 1.20f,
+ animationSpec = spring(
+ dampingRatio = Spring.DampingRatioNoBouncy,
+ stiffness = 800f,
+ )
+ )
+ rollScale.animateTo(
+ targetValue = 1f,
+ animationSpec = spring(
+ dampingRatio = 0.28f,
+ stiffness = 800f,
+ )
+ )
+ }
launch {
rollRotation.animateTo(
targetValue = rollRotation.value.let { it - it % 360 } + 360f * 3,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/preview/ContentPreview.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/preview/ContentPreview.kt
new file mode 100644
index 0000000..45b89c0
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/preview/ContentPreview.kt
@@ -0,0 +1,31 @@
+package com.pixelized.desktop.lwa.utils.preview
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.pixelized.desktop.lwa.theme.LwaTheme
+
+@Composable
+fun ContentPreview(
+ modifier: Modifier = Modifier,
+ paddingValues: PaddingValues = PaddingValues(),
+ content: @Composable () -> Unit,
+) {
+ LwaTheme {
+ Surface {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues = paddingValues)
+ .then(other = modifier),
+ contentAlignment = Alignment.Center,
+ content = { content() },
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/rememberKeyboardActions.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/rememberKeyboardActions.kt
new file mode 100644
index 0000000..b09b316
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/rememberKeyboardActions.kt
@@ -0,0 +1,9 @@
+package com.pixelized.desktop.lwa.utils
+
+import androidx.compose.foundation.text.KeyboardActionScope
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+
+@Composable
+fun rememberKeyboardActions(onAny: KeyboardActionScope.() -> Unit) = remember { KeyboardActions(onAny = onAny) }
\ No newline at end of file
diff --git a/composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/parser/ArithmeticParserTest.kt b/composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/parser/ArithmeticParserTest.kt
new file mode 100644
index 0000000..34729f2
--- /dev/null
+++ b/composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/parser/ArithmeticParserTest.kt
@@ -0,0 +1,156 @@
+package com.pixelized.desktop.lwa.parser
+
+import com.pixelized.desktop.lwa.parser.arithmetic.Arithmetic
+import com.pixelized.desktop.lwa.parser.arithmetic.ArithmeticParser
+import com.pixelized.desktop.lwa.parser.arithmetic.Instruction
+import kotlin.test.Test
+import kotlin.test.assertFails
+
+class ArithmeticParserTest {
+
+ @Test
+ fun testDiceInstructionParse() {
+ val parser = ArithmeticParser()
+
+ fun test(
+ instruction: String,
+ expectedModifier: Instruction.Dice.Modifier?,
+ expectedQuantity: Int,
+ expectedFaces: Int,
+ ) {
+ val dice = parser.parseInstruction(instruction = instruction)
+
+ assert(dice is Instruction.Dice) {
+ "Instruction should be ArithmeticInstruction.Dice but was: ${dice::class.java.simpleName}"
+ }
+ (dice as? Instruction.Dice)?.let {
+ assert(dice.modifier == expectedModifier) {
+ "$instruction modifier should be:\"$expectedModifier\", but was: ${dice.modifier}"
+ }
+ assert(dice.quantity == expectedQuantity) {
+ "$instruction quantity should be \"$expectedQuantity\" but was ${dice.quantity}"
+ }
+ assert(dice.faces == expectedFaces) {
+ "$instruction faces should be \"$expectedFaces\" but was ${dice.faces}"
+ }
+ }
+ }
+
+ test(
+ instruction = "1d100",
+ expectedModifier = null,
+ expectedQuantity = 1,
+ expectedFaces = 100,
+ )
+ test(
+ instruction = "a2d6",
+ expectedModifier = Instruction.Dice.Modifier.ADVANTAGE,
+ expectedQuantity = 2,
+ expectedFaces = 6,
+ )
+ test(
+ instruction = "d1d2",
+ expectedModifier = Instruction.Dice.Modifier.DISADVANTAGE,
+ expectedQuantity = 1,
+ expectedFaces = 2,
+ )
+ test(
+ instruction = "e6d6",
+ expectedModifier = Instruction.Dice.Modifier.EMPHASIS,
+ expectedQuantity = 6,
+ expectedFaces = 6,
+ )
+ }
+
+ @Test
+ fun testWordInstructionParse() {
+ val parser = ArithmeticParser()
+
+ ArithmeticParser.words.map { instruction ->
+ val word = parser.parseInstruction(instruction = instruction)
+
+ assert(word is Instruction.Word) {
+ "Instruction should be ArithmeticInstruction.Word but was: ${word::class.java.simpleName}"
+ }
+ (word as? Instruction.Word)?.let {
+ assert(it.name == instruction) {
+ "Instruction should be $instruction, but was ${it.name}"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testFlatInstructionParse() {
+ val parser = ArithmeticParser()
+
+ "100".let { instruction ->
+ val flat = parser.parseInstruction(instruction = instruction)
+
+ assert(flat is Instruction.Flat) {
+ "Instruction should be ArithmeticInstruction.Flat but was: ${flat::class.java.simpleName}"
+ }
+ (flat as? Instruction.Flat)?.let {
+ assert("${it.value}" == instruction) {
+ "Instruction should be $instruction, but was ${it.value}"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testFailedInstructionParse() {
+ val parser = ArithmeticParser()
+ assertFails(
+ message = "Instruction parse should failed with UnknownInstruction",
+ ) {
+ parser.parseInstruction(instruction = "a110")
+ }
+ }
+
+ @Test
+ fun testRollParse() {
+ val parser = ArithmeticParser()
+
+ fun test(
+ arithmetics: Arithmetic,
+ expectedSign: Int,
+ expectedInstruction: Instruction,
+ ) {
+ assert(arithmetics.sign == expectedSign) {
+ "Arithmetic sign should be $expectedSign but was: ${arithmetics.sign}"
+ }
+ assert(arithmetics.instruction == expectedInstruction) {
+ "Arithmetic instruction should be $expectedInstruction but was: ${arithmetics.instruction}"
+ }
+ }
+
+ val instructions = parser.parse(value = "1+1d6+2-BDC+BDD")
+
+ test(
+ arithmetics = instructions[0],
+ expectedSign = 1,
+ expectedInstruction = Instruction.Flat(value = 1),
+ )
+ test(
+ arithmetics = instructions[1],
+ expectedSign = 1,
+ expectedInstruction = Instruction.Dice(quantity = 1, faces = 6, modifier = null),
+ )
+ test(
+ arithmetics = instructions[2],
+ expectedSign = 1,
+ expectedInstruction = Instruction.Flat(value = 2),
+ )
+ test(
+ arithmetics = instructions[3],
+ expectedSign = -1,
+ expectedInstruction = Instruction.Word.BDC,
+ )
+ test(
+ arithmetics = instructions[4],
+ expectedSign = 1,
+ expectedInstruction = Instruction.Word.BDD,
+ )
+ }
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index d000d57..3cf23e9 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -15,9 +15,12 @@ composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "k
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+
[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
+compose-desktop-preview = { group = "org.jetbrains.compose.ui", name = "ui-tooling-preview", version.ref = "compose-multiplatform" }
+
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
androidx-navigation-compose = { group = "org.jetbrains.androidx.navigation", name = "navigation-compose", version.ref = "androidx-navigation" }