Refactor the character sheet.
This commit is contained in:
parent
52f7f8333b
commit
51021d41d5
37 changed files with 1996 additions and 529 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
<string name="character_sheet_edit__title">Création de personnage</string>
|
||||
<string name="character_sheet_edit__name_placeholder">Nom</string>
|
||||
<string name="character_sheet_edit__add_roll_action">Ajouter un lancer</string>
|
||||
<string name="character_sheet_edit__add_roll_action">Ajouter une action</string>
|
||||
<string name="character_sheet_edit__save_action">Sauvegarder</string>
|
||||
<string name="character_sheet_edit__characteristics__title">Caractéristiques</string>
|
||||
<string name="character_sheet_edit__characteristics__str">Force</string>
|
||||
|
|
@ -35,8 +35,15 @@
|
|||
<string name="character_sheet_edit__sub_characteristics__power_point">Points de pouvoir</string>
|
||||
<string name="character_sheet_edit__sub_characteristics__damage_bonus">Bonus aux dégats</string>
|
||||
<string name="character_sheet_edit__sub_characteristics__armor">Armure</string>
|
||||
<string name="character_sheet_edit__skills__common_title">Compétences communes</string>
|
||||
<string name="character_sheet_edit__skills__special_title">Compétences spéciales</string>
|
||||
<string name="character_sheet_edit__skills__special_action">Ajouter une compétence spéciale</string>
|
||||
<string name="character_sheet_edit__skills__magic_title">Compétences magiques</string>
|
||||
<string name="character_sheet_edit__skills__magic_action">Ajouter une compétence magique</string>
|
||||
<string name="character_sheet_edit__skills__base_label">Base</string>
|
||||
<string name="character_sheet_edit__skills__bonus_label">Bonus</string>
|
||||
<string name="character_sheet_edit__skills__level_label">Niveau</string>
|
||||
<string name="character_sheet_edit__skills__title">Compétences</string>
|
||||
<string name="character_sheet_edit__skills__add_action">Ajouter une compétence</string>
|
||||
<string name="character_sheet_edit__skills__combat">Bagarre</string>
|
||||
<string name="character_sheet_edit__skills__dodge">Esquive</string>
|
||||
<string name="character_sheet_edit__skills__grab">Saisie</string>
|
||||
|
|
@ -57,6 +64,9 @@
|
|||
<string name="character_sheet_edit__occupation__add_action">Ajouter une occupation</string>
|
||||
<string name="character_sheet_edit__magic__title">Compétences magiques</string>
|
||||
<string name="character_sheet_edit__magic__add_action">Ajouter une compétence magique</string>
|
||||
<string name="character_sheet_edit__delete__label">Supprimer</string>
|
||||
<string name="character_sheet_edit__occupation__label">Compétence d'occupation</string>
|
||||
|
||||
|
||||
<string name="character_sheet__diminished__label">État diminuer</string>
|
||||
<string name="character_sheet__edit__label">Modifier</string>
|
||||
|
|
@ -74,9 +84,9 @@
|
|||
<string name="character_sheet__sub_characteristics__power_point">Points de pouvoir</string>
|
||||
<string name="character_sheet__sub_characteristics__damage_bonus">Bonus aux dégats</string>
|
||||
<string name="character_sheet__sub_characteristics__armor">Armure</string>
|
||||
<string name="character_sheet__skills__title">Compétences</string>
|
||||
<string name="character_sheet__occupations_title">Occupations</string>
|
||||
<string name="character_sheet__magics__title">Compétences magiques</string>
|
||||
<string name="character_sheet__skills__common_title">Compétences communes</string>
|
||||
<string name="character_sheet__skills__special_title">Compétences spéciales</string>
|
||||
<string name="character_sheet__skills__magic_title">Compétences magiques</string>
|
||||
<string name="character_sheet__delete_dialog__title">Supprimer la feuille de personnage</string>
|
||||
<string name="character_sheet__delete_dialog__description">Êtes-vous sûr de vouloir supprimer "%1$s" ?</string>
|
||||
<string name="character_sheet__delete_dialog__confirm_action">Confirmer</string>
|
||||
|
|
@ -93,7 +103,7 @@
|
|||
<string name="tooltip__sub_characteristics__movement">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.</string>
|
||||
<string name="tooltip__sub_characteristics__hit_point">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.</string>
|
||||
<string name="tooltip__sub_characteristics__power_point">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.</string>
|
||||
<string name="tooltip__sub_characteristics__bonus_damage">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.</string>
|
||||
<string name="tooltip__sub_characteristics__bonus_damage">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).</string>
|
||||
<string name="tooltip__sub_characteristics__armor">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.</string>
|
||||
|
||||
<string name="tooltip__skills__combat">Attaque en combat à mains nues. Une attaque réussie inflige 1D3 + BDGT.</string>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
val parserDependencies
|
||||
get() = module {
|
||||
factoryOf(::ArithmeticParser)
|
||||
}
|
||||
|
||||
val useCaseDependencies
|
||||
get() = module {
|
||||
factoryOf(::DamageBonusUseCase)
|
||||
factoryOf(::SkillValueComputationUseCase)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Window> {
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = "")
|
||||
class CharacterSheetCreateWindow : Window(
|
||||
title = "",
|
||||
size = size,
|
||||
) {
|
||||
companion object {
|
||||
val size = DpSize(600.dp, 900.dp)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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*(?<operator>[-+])?\s*(?<payload>[^-+]*)"""
|
||||
)
|
||||
private val diceParser = Regex(
|
||||
"""^(?<modifier>[ade])?(?<quantity>\d+)[dD](?<faces>\d+)"""
|
||||
)
|
||||
private val wordParser = Regex(
|
||||
"""^(?<word>${words.joinToString(separator = "|") { it }})"""
|
||||
)
|
||||
private val flatParser = Regex(
|
||||
"""(?<flat>\d+)"""
|
||||
)
|
||||
|
||||
@Throws(Instruction.UnknownInstruction::class)
|
||||
fun parse(value: String): List<Arithmetic> {
|
||||
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<Arithmetic>,
|
||||
): 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<String> = Word.entries.map { it.name }
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
)
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class CharacterSheetStore(
|
|||
fun save(sheets: List<CharacterSheet>) {
|
||||
val json = try {
|
||||
sheets
|
||||
.map(factory::convertLastJsonVersion)
|
||||
.map(factory::convertToJson)
|
||||
.let(Json::encodeToString)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
|
|
|
|||
|
|
@ -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<Skill>,
|
||||
// occupations
|
||||
val occupations: List<Skill>,
|
||||
// magic skill
|
||||
val magics: List<Skill>,
|
||||
// attack
|
||||
val rolls: List<Roll>,
|
||||
val commonSkills: List<Skill>,
|
||||
val specialSkills: List<Skill>,
|
||||
val magicSkills: List<Skill>,
|
||||
// actions
|
||||
val actions: List<Roll>,
|
||||
) {
|
||||
data class Skill(
|
||||
val id: String,
|
||||
val label: String,
|
||||
val value: Int,
|
||||
val base: List<Arithmetic>,
|
||||
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<Arithmetic>,
|
||||
)
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Skill>,
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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<OptionUio> = 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<OptionUio>.occupation: Boolean
|
||||
get() = this.firstNotNullOfOrNull {
|
||||
if (it is CheckedOption.OccupationOption) it.checked.value else null
|
||||
} ?: false
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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<Characteristic>,
|
||||
val subCharacteristics: List<Characteristic>,
|
||||
val skills: List<Node>,
|
||||
val occupations: List<Node>,
|
||||
val magics: List<Node>,
|
||||
val rolls: List<Roll>,
|
||||
val commonSkills: List<Node>,
|
||||
val specialSKills: List<Node>,
|
||||
val magicsSkills: List<Node>,
|
||||
val actions: List<Roll>,
|
||||
) {
|
||||
@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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<Int>().let {
|
||||
max(0, min(it, currentSheet?.currentHp ?: it))
|
||||
},
|
||||
overrideMaxPP = editedSheet.maxPP.value.value.isNotBlank(),
|
||||
maxPP = editedSheet.maxPP.unpack(),
|
||||
currentPp = editedSheet.maxPP.unpack<Int>().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<Int>,
|
||||
): 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 <reified T> FieldUio.unpack(): T {
|
||||
val tmp = value.value.ifBlank { valuePlaceHolder.value }
|
||||
return when (T::class) {
|
||||
|
|
|
|||
|
|
@ -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<SkillGroup>,
|
||||
val rolls: List<FieldUio>,
|
||||
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<BaseSkillFieldUio>,
|
||||
val specialSkills: List<SkillFieldUio>,
|
||||
val magicSkills: List<SkillFieldUio>,
|
||||
val actions: List<FieldUio>,
|
||||
) {
|
||||
@Stable
|
||||
data class SkillGroup(
|
||||
val title: String,
|
||||
val type: Type,
|
||||
val action: String? = null,
|
||||
val fields: List<FieldUio>,
|
||||
) {
|
||||
@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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CharacterSheetEditPageUio> 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<FieldUio>().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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Int>,
|
||||
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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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" },
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<OptionUio>,
|
||||
)
|
||||
|
||||
@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<OptionUio> = 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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Boolean>,
|
||||
onOption: () -> Unit,
|
||||
) : OptionUio(onOption = onOption) {
|
||||
class OccupationOption(label: String, checked: State<Boolean>, 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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<RollResultUio?> get() = _rollResult
|
||||
|
||||
val rollRotation = Animatable(0f)
|
||||
val rollScale = Animatable(1f)
|
||||
|
||||
private val _rollDifficulty = mutableStateOf<DifficultyUio?>(null)
|
||||
val rollDifficulty: State<DifficultyUio?> 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,
|
||||
|
|
|
|||
|
|
@ -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() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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" }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue