Add tooltip to the character sheet.

This commit is contained in:
Thomas Andres Gomez 2024-11-16 00:11:28 +01:00
parent 7fc99f5799
commit c7fe440f8f
6 changed files with 334 additions and 83 deletions

View file

@ -0,0 +1,79 @@
package com.pixelized.desktop.lwa.composable.tooltip
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.TooltipArea
import androidx.compose.foundation.TooltipPlacement
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
class TooltipUio(
val title: String?,
val description: String,
)
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun TooltipLayout(
modifier: Modifier = Modifier,
delayMillis: Int = 1000,
tooltipPlacement: TooltipPlacement = TooltipPlacement.CursorPoint(DpOffset(0.dp, 16.dp)),
tooltip: TooltipUio?,
content: @Composable () -> Unit,
) {
when (tooltip) {
null -> Box(
modifier = modifier,
content = { content() },
)
else -> TooltipArea(
modifier = modifier,
tooltip = { Tooltip(tooltip = tooltip) },
content = content,
delayMillis = delayMillis,
tooltipPlacement = tooltipPlacement,
)
}
}
@Composable
fun Tooltip(
modifier: Modifier = Modifier,
tooltip: TooltipUio,
) {
Surface(
modifier = Modifier.padding(16.dp).then(other = modifier),
color = MaterialTheme.colors.surface,
elevation = 2.dp,
shape = remember { RoundedCornerShape(4.dp) }
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
tooltip.title?.let {
Text(
style = MaterialTheme.typography.body2,
fontWeight = FontWeight.Bold,
text = it
)
}
Text(
style = MaterialTheme.typography.caption,
text = tooltip.description
)
}
}
}

View file

@ -43,4 +43,23 @@ data class CharacterSheet(
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"
}
}

View file

@ -1,5 +1,6 @@
package com.pixelized.desktop.lwa.screen.characterSheet.detail
import com.pixelized.desktop.lwa.composable.tooltip.TooltipUio
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio.Characteristic
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio.Node
@ -16,6 +17,34 @@ import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_cha
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hit_point
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__movement
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__power_point
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__charisma
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__constitution
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__dexterity
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__height
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__intelligence
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__power
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__strength
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__acrobatics
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__aid
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__athletics
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__bargain
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__combat
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__discretion
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__dodge
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__empathy
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__grab
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__intimidation
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__perception
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__persuasion
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__search
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__sleight_of_hand
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__spiel
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__throw
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__armor
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__bonus_damage
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__hit_point
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
@ -38,42 +67,70 @@ class CharacterSheetFactory {
id = "STR",
label = getString(Res.string.character_sheet__characteristics__str),
value = "${model.strength}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__str),
description = getString(Res.string.tooltip__characteristics__strength),
),
editable = false,
),
Characteristic(
id = "DEX",
label = getString(Res.string.character_sheet__characteristics__dex),
value = "${model.dexterity}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__dex),
description = getString(Res.string.tooltip__characteristics__dexterity),
),
editable = false,
),
Characteristic(
id = "CON",
label = getString(Res.string.character_sheet__characteristics__con),
value = "${model.constitution}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__con),
description = getString(Res.string.tooltip__characteristics__constitution),
),
editable = false,
),
Characteristic(
id = "HEI",
label = getString(Res.string.character_sheet__characteristics__hei),
value = "${model.height}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__hei),
description = getString(Res.string.tooltip__characteristics__height),
),
editable = false,
),
Characteristic(
id = "INT",
label = getString(Res.string.character_sheet__characteristics__int),
value = "${model.intelligence}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__int),
description = getString(Res.string.tooltip__characteristics__intelligence),
),
editable = false,
),
Characteristic(
id = "POW",
label = getString(Res.string.character_sheet__characteristics__pow),
value = "${model.power}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__pow),
description = getString(Res.string.tooltip__characteristics__power),
),
editable = false,
),
Characteristic(
id = "CHA",
label = getString(Res.string.character_sheet__characteristics__cha),
value = "${model.charisma}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__characteristics__cha),
description = getString(Res.string.tooltip__characteristics__charisma),
),
editable = false,
),
),
@ -82,39 +139,84 @@ class CharacterSheetFactory {
id = "MOV",
label = getString(Res.string.character_sheet__sub_characteristics__movement),
value = "${model.movement}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__movement),
description = getString(Res.string.tooltip__sub_characteristics__movement),
),
editable = false,
),
Characteristic(
id = HP,
label = getString(Res.string.character_sheet__sub_characteristics__hit_point),
value = "${model.currentHp}/${model.maxHp}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__hit_point),
description = getString(Res.string.tooltip__sub_characteristics__hit_point),
),
editable = true,
),
Characteristic(
id = PP,
label = getString(Res.string.character_sheet__sub_characteristics__power_point),
value = "${model.currentPP}/${model.maxPP}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__power_point),
description = getString(Res.string.tooltip__sub_characteristics__power_point),
),
editable = true,
),
Characteristic(
id = "DMG",
label = getString(Res.string.character_sheet__sub_characteristics__damage_bonus),
value = model.damageBonus,
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__damage_bonus),
description = getString(Res.string.tooltip__sub_characteristics__bonus_damage),
),
editable = false,
),
Characteristic(
id = "ARMOR",
label = getString(Res.string.character_sheet__sub_characteristics__armor),
value = "${model.armor}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__armor),
description = getString(Res.string.tooltip__sub_characteristics__armor),
),
editable = false,
),
),
skills = model.skills.mapNotNull {
if (it.value > 0) {
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 = it.label,
value = it.value.diminished(diminished),
used = it.used,
label = skill.label,
value = skill.value.diminished(diminished),
tooltips = description?.let {
TooltipUio(
title = skill.label,
description = it,
)
},
used = skill.used,
)
} else {
null

View file

@ -7,6 +7,7 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@ -55,6 +56,8 @@ import com.pixelized.desktop.lwa.LocalWindowController
import com.pixelized.desktop.lwa.composable.blur.BlurContent
import com.pixelized.desktop.lwa.composable.blur.BlurContentController
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.composable.tooltip.TooltipLayout
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
@ -76,7 +79,6 @@ import lwacharactersheet.composeapp.generated.resources.ic_skull_32dp
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 CharacterSheetPageUio(
@ -95,12 +97,14 @@ data class CharacterSheetPageUio(
val label: String,
val value: String,
val editable: Boolean,
val tooltips: TooltipUio?,
)
@Stable
data class Node(
val label: String,
val value: Int,
val tooltips: TooltipUio? = null,
val used: Boolean,
)
@ -111,7 +115,6 @@ data class CharacterSheetPageUio(
)
}
@OptIn(KoinExperimentalAPI::class)
@Composable
fun CharacterSheetPage(
viewModel: CharacterSheetViewModel = koinViewModel(),
@ -277,7 +280,7 @@ fun CharacterSheetPageContent(
)
},
actions = {
var showMenu = remember { mutableStateOf(false) }
val showMenu = remember { mutableStateOf(false) }
Box {
IconButton(
onClick = onDiminished,
@ -493,6 +496,7 @@ fun CharacterSheetPageContent(
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun Stat(
modifier: Modifier = Modifier,
@ -500,30 +504,36 @@ private fun Stat(
characteristic: CharacterSheetPageUio.Characteristic,
onClick: () -> Unit,
) {
DecoratedBox(
modifier = Modifier
.clickable(onClick = onClick)
.padding(paddingValues = paddingValues)
.then(other = modifier),
) {
Text(
modifier = Modifier.align(alignment = Alignment.TopCenter),
style = MaterialTheme.typography.caption,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = characteristic.label,
)
Text(
modifier = Modifier.align(alignment = Alignment.Center),
style = MaterialTheme.typography.h3,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
color = MaterialTheme.colors.primary,
text = characteristic.value
)
}
TooltipLayout(
tooltip = characteristic.tooltips,
content = {
DecoratedBox(
modifier = Modifier
.clickable(onClick = onClick)
.padding(paddingValues = paddingValues)
.then(other = modifier),
) {
Text(
modifier = Modifier.align(alignment = Alignment.TopCenter),
style = MaterialTheme.typography.caption,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = characteristic.label,
)
Text(
modifier = Modifier.align(alignment = Alignment.Center),
style = MaterialTheme.typography.h3,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
color = MaterialTheme.colors.primary,
text = characteristic.value
)
}
},
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun SubCharacteristics(
modifier: Modifier = Modifier,
@ -531,31 +541,37 @@ private fun SubCharacteristics(
characteristic: CharacterSheetPageUio.Characteristic,
onCharacteristic: ((characteristic: CharacterSheetPageUio.Characteristic) -> Unit)?,
) {
Row(
modifier = Modifier
.clickable(enabled = characteristic.editable && onCharacteristic != null) {
onCharacteristic?.invoke(characteristic)
TooltipLayout(
tooltip = characteristic.tooltips,
content = {
Row(
modifier = Modifier
.clickable(enabled = characteristic.editable && onCharacteristic != null) {
onCharacteristic?.invoke(characteristic)
}
.padding(paddingValues = paddingValues).then(other = modifier),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
style = MaterialTheme.typography.body1,
text = characteristic.label
)
Text(
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold,
color = when (characteristic.editable && onCharacteristic != null) {
true -> MaterialTheme.colors.primary
else -> MaterialTheme.colors.onSurface
},
text = characteristic.value,
)
}
.padding(paddingValues = paddingValues).then(other = modifier),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
style = MaterialTheme.typography.body1,
text = characteristic.label
)
Text(
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold,
color = when (characteristic.editable && onCharacteristic != null) {
true -> MaterialTheme.colors.primary
else -> MaterialTheme.colors.onSurface
},
text = characteristic.value,
)
}
},
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun Skill(
modifier: Modifier = Modifier,
@ -564,36 +580,41 @@ private fun Skill(
onClick: () -> Unit,
onUse: () -> Unit,
) {
Row(
modifier = Modifier
.clickable(onClick = onClick)
.padding(paddingValues = paddingValues)
.then(other = modifier),
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = node.label
)
Text(
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.primary,
text = "${node.value}",
)
Checkbox(
modifier = Modifier.size(size = 32.dp),
checked = node.used,
colors = CheckboxDefaults.colors(
checkedColor = MaterialTheme.colors.primary,
),
onCheckedChange = { onUse() },
)
}
TooltipLayout(
tooltip = node.tooltips,
content = {
Row(
modifier = Modifier
.clickable(onClick = onClick)
.padding(paddingValues = paddingValues)
.then(other = modifier),
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = node.label
)
Text(
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.primary,
text = "${node.value}",
)
Checkbox(
modifier = Modifier.size(size = 32.dp),
checked = node.used,
colors = CheckboxDefaults.colors(
checkedColor = MaterialTheme.colors.primary,
),
onCheckedChange = { onUse() },
)
}
},
)
}
@Composable