diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts
index 0bd91d4..0cc425a 100644
--- a/composeApp/build.gradle.kts
+++ b/composeApp/build.gradle.kts
@@ -34,6 +34,7 @@ kotlin {
// injection
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
+ implementation(libs.engawapg.zoomable)
// composable component.
implementation(libs.coil.compose)
implementation(libs.coil.network.ktor)
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_center_focus_weak_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_center_focus_weak_24dp.xml
new file mode 100644
index 0000000..a92214c
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_center_focus_weak_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_more_down_24dp.svg b/composeApp/src/commonMain/composeResources/drawable/ic_more_down_24dp.svg
new file mode 100644
index 0000000..febe0eb
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_more_down_24dp.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_zoom_in_map_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_zoom_in_map_24dp.xml
new file mode 100644
index 0000000..144dc6c
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_zoom_in_map_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_zoom_out_map_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_zoom_out_map_24dp.xml
new file mode 100644
index 0000000..6cdc80e
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_zoom_out_map_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/image_dahome_maps.webp b/composeApp/src/commonMain/composeResources/drawable/image_dahome_maps.webp
new file mode 100644
index 0000000..28f7f73
Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/image_dahome_maps.webp differ
diff --git a/composeApp/src/commonMain/composeResources/drawable/image_dahome_regions.webp b/composeApp/src/commonMain/composeResources/drawable/image_dahome_regions.webp
new file mode 100644
index 0000000..4927211
Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/image_dahome_regions.webp differ
diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index 4f9d158..dcb2884 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -269,12 +269,6 @@
Délai pour les Dés dynamiques
Délai après lequel les dés dynamiques disparaissent.
Chatlog options
- Afficher automatiquement le chat
- Affiche automatiquement le chat lors de la réception d'un message
- Cacher automatiquement le chat
- Cache automatiquement le chat au bout d'un certain temps
- Délai pour cacher le chat
- Délai après lequel le chat disparaît
Défilement automatique
Défilement automatique du chat vers le dernier message reçu lors de la réception de ce dernier.
Nombre de lignes de textes visibles
@@ -329,5 +323,17 @@
Consommable
Ajouter une alteration
Édition de personnage
+ Synchronisation du serveur
+ Demander au serveur d'invalider son cache
+ Soigner les personnages joueurs
+ Cette action réinitialisera les points de vie, de pouvoir et d'état diminué de chaque personnage joueur présent dans le groupe.
+ Cacher le groupe de personnages joueur
+ Cacher le panneau latéral gauche pour tous les joueurs.
+ Montrer les personnages joueurs
+ Montrer le panneau latéral gauche pour tous les joueurs.
+ Cacher le groupe de npcs
+ Cacher le panneau latéral droit pour tous les joueurs.
+ Montrer le groupe de npcs
+ Montrer le panneau latéral droit pour tous les joueurs.
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt
index 78e7204..650a4cf 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt
@@ -40,13 +40,14 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.Characte
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.CharacterDetailInventoryFactory
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc.NpcRibbonViewModel
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel
+import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.NpcRibbonViewModel
+import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbonViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.text.TextMessageFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.links.ResourcesViewModel
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterViewModel
+import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionUseCase
import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionViewModel
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditFactory
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditViewModel
@@ -198,4 +199,5 @@ val viewModelDependencies
val useCaseDependencies
get() = module {
factoryOf(::SettingsUseCase)
+ factoryOf(::GMActionUseCase)
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt
index 8924ff2..f9b4f03 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt
@@ -3,6 +3,7 @@ package com.pixelized.desktop.lwa.repository.settings
import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.repository.settings.model.SettingsJson
import com.pixelized.desktop.lwa.repository.settings.model.SettingsJsonV1
+import com.pixelized.desktop.lwa.repository.settings.model.SettingsJsonV2
import com.pixelized.desktop.lwa.usecase.SettingsUseCase
@@ -12,15 +13,13 @@ class SettingsFactory(
fun convertToJson(
settings: Settings,
): SettingsJson {
- return SettingsJsonV1(
+ return SettingsJsonV2(
host = settings.network.host,
port = settings.network.port,
playerName = settings.playerName,
dynamicDice = settings.portrait.dynamicDice,
dynamicDiceDelay = settings.portrait.dynamicDiceDelay,
- autoHideChat = settings.chat.autoHideChat,
- autoHideDelay = settings.chat.autoHideDelay,
- autoShowChat = settings.chat.autoShowChat,
+ showChat = settings.chat.showChat,
autoScrollChat = settings.chat.autoScrollChat,
maxLineCount = settings.chat.maxLineCount,
isAdmin = settings.isAdmin,
@@ -33,6 +32,7 @@ class SettingsFactory(
): Settings {
return when (json) {
is SettingsJsonV1 -> convertFromJsonV1(json)
+ is SettingsJsonV2 -> convertFromJsonV2(json)
}
}
@@ -51,9 +51,31 @@ class SettingsFactory(
dynamicDiceDelay = json.dynamicDiceDelay ?: default.portrait.dynamicDiceDelay,
),
chat = Settings.Chat(
- autoHideChat = json.autoHideChat ?: default.chat.autoHideChat,
- autoHideDelay = json.autoHideDelay ?: default.chat.autoHideDelay,
- autoShowChat = json.autoShowChat ?: default.chat.autoShowChat,
+ showChat = default.chat.showChat,
+ autoScrollChat = json.autoScrollChat ?: default.chat.autoScrollChat,
+ maxLineCount = json.maxLineCount ?: default.chat.maxLineCount,
+ ),
+ isAdmin = json.isAdmin ?: default.isAdmin,
+ isGameMaster = json.isGameMaster ?: default.isGameMaster,
+ )
+ }
+
+ private fun convertFromJsonV2(
+ json: SettingsJsonV2,
+ ): Settings {
+ val default = useCase.defaultSettings()
+ return Settings(
+ playerName = json.playerName ?: default.playerName,
+ network = Settings.Network(
+ host = json.host ?: default.network.host,
+ port = json.port ?: default.network.port,
+ ),
+ portrait = Settings.Portrait(
+ dynamicDice = json.dynamicDice ?: default.portrait.dynamicDice,
+ dynamicDiceDelay = json.dynamicDiceDelay ?: default.portrait.dynamicDiceDelay,
+ ),
+ chat = Settings.Chat(
+ showChat = json.showChat ?: default.chat.showChat,
autoScrollChat = json.autoScrollChat ?: default.chat.autoScrollChat,
maxLineCount = json.maxLineCount ?: default.chat.maxLineCount,
),
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt
index 80fc9ee..2ef636a 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt
@@ -14,9 +14,7 @@ data class Settings(
)
data class Chat(
- val autoHideChat: Boolean,
- val autoHideDelay: Int,
- val autoShowChat: Boolean,
+ val showChat: Boolean,
val autoScrollChat: Boolean,
val maxLineCount: Int,
)
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/SettingsJsonV2.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/SettingsJsonV2.kt
new file mode 100644
index 0000000..7e54773
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/SettingsJsonV2.kt
@@ -0,0 +1,17 @@
+package com.pixelized.desktop.lwa.repository.settings.model
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SettingsJsonV2(
+ val host: String?,
+ val port: Int?,
+ val playerName: String?,
+ val dynamicDice: Boolean?,
+ val dynamicDiceDelay: Int?,
+ val showChat: Boolean?,
+ val autoScrollChat: Boolean?,
+ val maxLineCount: Int?,
+ val isGameMaster: Boolean?,
+ val isAdmin: Boolean?,
+) : SettingsJson
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/LwaDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/LwaDialog.kt
index 856450d..80e8b46 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/LwaDialog.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/LwaDialog.kt
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
@@ -18,6 +19,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
+import com.pixelized.desktop.lwa.LocalBlurController
+import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
@@ -29,6 +32,7 @@ object LwaDialogDefault {
@Composable
fun LwaDialog(
modifier: Modifier = Modifier,
+ blur: BlurContentController? = LocalBlurController.current,
paddings: PaddingValues = LwaDialogDefault.paddings,
color: Color = MaterialTheme.colors.surface,
state: State,
@@ -37,6 +41,16 @@ fun LwaDialog(
content: @Composable BoxScope.(T) -> Unit,
) {
state.value?.let { dialog ->
+
+ blur?.let {
+ DisposableEffect("LwaDialog") {
+ blur.show()
+ onDispose {
+ blur.hide()
+ }
+ }
+ }
+
Dialog(
onDismissRequest = onDismissRequest,
content = {
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/alterteration/CharacterSheetAlterationDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/alterteration/CharacterSheetAlterationDialog.kt
index 23ead9d..8dea0f1 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/alterteration/CharacterSheetAlterationDialog.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/alterteration/CharacterSheetAlterationDialog.kt
@@ -19,6 +19,7 @@ import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
@@ -28,13 +29,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
-import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler
-import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
+import com.pixelized.desktop.lwa.LocalBlurController
+import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMFilterHeader
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
import com.pixelized.desktop.lwa.ui.theme.lwa
-import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_close_24dp
@@ -52,12 +52,23 @@ data class CharacterSheetAlterationDialogUio(
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun CharacterSheetAlterationDialog(
+ blur: BlurContentController? = LocalBlurController.current,
dialog: State,
onTag: (String) -> Unit,
onAlteration: (characterSheetId: String, alterationId: String, active: Boolean) -> Unit,
onDismissRequest: () -> Unit,
) {
dialog.value?.let {
+
+ blur?.let {
+ DisposableEffect("LwaDialog") {
+ blur.show()
+ onDispose {
+ blur.hide()
+ }
+ }
+ }
+
Dialog(
properties = DialogProperties(
usePlatformDefaultWidth = false,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/confirmation/ConfirmationDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/confirmation/ConfirmationDialog.kt
new file mode 100644
index 0000000..7aab76c
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/confirmation/ConfirmationDialog.kt
@@ -0,0 +1,98 @@
+package com.pixelized.desktop.lwa.ui.composable.confirmation
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.pixelized.desktop.lwa.LocalBlurController
+import com.pixelized.desktop.lwa.ui.composable.character.LwaDialog
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action
+import lwacharactersheet.composeapp.generated.resources.dialog__confirm_action
+import org.jetbrains.compose.resources.stringResource
+
+@Stable
+data class ConfirmationDialogUio(
+ val title: String,
+ val description: String,
+ val onConfirmRequest: () -> Unit,
+ val onDismissRequest: () -> Unit,
+)
+
+@Stable
+object ConfirmationDialogDefault {
+
+ @Stable
+ val paddings = PaddingValues(start = 16.dp, top = 16.dp, end = 16.dp)
+
+ @Stable
+ val spacings: Dp = 8.dp
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun ConfirmationDialog(
+ modifier: Modifier = Modifier,
+ paddingValues: PaddingValues = ConfirmationDialogDefault.paddings,
+ spacing: Dp = ConfirmationDialogDefault.spacings,
+ dialog: State,
+) {
+ LwaDialog(
+ modifier = modifier,
+ blur = LocalBlurController.current,
+ state = dialog,
+ onDismissRequest = { dialog.value?.onDismissRequest?.invoke() },
+ onConfirm = { dialog.value?.onConfirmRequest?.invoke() },
+ ) {
+ Column(
+ modifier = Modifier.padding(paddingValues = paddingValues),
+ verticalArrangement = Arrangement.spacedBy(space = spacing),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ style = MaterialTheme.typography.caption,
+ text = it.title,
+ )
+ Text(
+ style = MaterialTheme.typography.body1,
+ text = it.description,
+ )
+ Row(
+ modifier = Modifier.align(alignment = Alignment.End),
+ horizontalArrangement = Arrangement.spacedBy(
+ space = spacing / 2,
+ alignment = Alignment.End,
+ ),
+ ) {
+ TextButton(
+ onClick = it.onDismissRequest,
+ ) {
+ Text(
+ color = MaterialTheme.colors.primaryVariant,
+ text = stringResource(Res.string.dialog__cancel_action)
+ )
+ }
+ TextButton(
+ onClick = it.onConfirmRequest,
+ ) {
+ Text(
+ color = MaterialTheme.colors.primary,
+ text = stringResource(Res.string.dialog__confirm_action)
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Camera.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Camera.kt
new file mode 100644
index 0000000..c9eae2f
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Camera.kt
@@ -0,0 +1,101 @@
+package com.pixelized.desktop.lwa.ui.composable.scene
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.fastRoundToInt
+
+@Stable
+class Camera(
+ private val initialZoom: Float = 2f,
+ private val initialOffset: IntOffset = IntOffset.Zero,
+) {
+ private var _zoom = Animatable(
+ initialValue = initialZoom,
+ typeConverter = Float.VectorConverter,
+ )
+ val zoom: Float get() = _zoom.value
+
+ private var _offset = Animatable(
+ initialValue = initialOffset,
+ typeConverter = IntOffset.VectorConverter,
+ )
+ val offset: IntOffset by derivedStateOf {
+ _offset.value + IntOffset(
+ x = (_sceneSize.width - cameraSizeZoomed.width) / 2,
+ y = (_sceneSize.height - cameraSizeZoomed.height) / 2,
+ )
+ }
+
+ private var _sceneSize: IntSize by mutableStateOf(IntSize.Zero)
+ private var _cameraSize: IntSize by mutableStateOf(IntSize.Zero)
+ val cameraSize: IntSize get() = _cameraSize
+ val cameraSizeZoomed: IntSize by derivedStateOf {
+ IntSize(
+ width = (cameraSize.width * zoom).fastRoundToInt(),
+ height = (cameraSize.height * zoom).fastRoundToInt(),
+ )
+ }
+
+ fun changeSizes(
+ sceneSize: IntSize,
+ cameraSize: IntSize,
+ ) {
+ _cameraSize = cameraSize
+ _sceneSize = sceneSize
+ }
+
+ suspend fun handlePanning(
+ delta: Offset,
+ snap: Boolean,
+ ) {
+ val value = _offset.value - IntOffset(
+ x = (delta.x * zoom).fastRoundToInt(),
+ y = (delta.y * zoom).fastRoundToInt(),
+ )
+ when {
+ snap -> _offset.snapTo(targetValue = value)
+ else -> _offset.animateTo(targetValue = value)
+ }
+ }
+
+ suspend fun handleZoom(
+ zoomIn: Boolean,
+ power: Float,
+ snap: Boolean = false,
+ ) {
+ val value = _zoom.value * when {
+ zoomIn -> 1f - power
+ else -> 1f + power
+ }
+ when {
+ snap -> _zoom.snapTo(targetValue = value)
+ else -> _zoom.animateTo(targetValue = value)
+ }
+ }
+
+ suspend fun resetPosition(
+ snap: Boolean = false,
+ ) {
+ when (snap) {
+ true -> _offset.snapTo(targetValue = initialOffset)
+ else -> _offset.animateTo(targetValue = initialOffset)
+ }
+ }
+
+ suspend fun resetZoom(
+ snap: Boolean = false,
+ ) {
+ when (snap) {
+ true -> _zoom.snapTo(targetValue = initialZoom)
+ else -> _zoom.animateTo(targetValue = initialZoom)
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/FogOfWar.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/FogOfWar.kt
new file mode 100644
index 0000000..31e9e09
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/FogOfWar.kt
@@ -0,0 +1,9 @@
+package com.pixelized.desktop.lwa.ui.composable.scene
+
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.graphics.Color
+
+@Stable
+data class FogOfWar(
+ val color: Color = Color.Black.copy(alpha = 0.5f),
+)
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Layout.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Layout.kt
new file mode 100644
index 0000000..e211690
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Layout.kt
@@ -0,0 +1,41 @@
+package com.pixelized.desktop.lwa.ui.composable.scene
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+
+@Stable
+class Layout(
+ val texture: ImageBitmap,
+ val offset: IntOffset = IntOffset.Zero,
+ val size: IntSize = IntSize(texture.width, texture.height),
+ private val initialAlpha: Float = 1f,
+) {
+ private val _alpha = Animatable(
+ initialValue = initialAlpha,
+ typeConverter = Float.VectorConverter,
+ )
+ val alpha get() = _alpha.value
+
+ suspend fun alpha(
+ alpha: Float,
+ snap: Boolean = false,
+ ) {
+ when (snap) {
+ true -> _alpha.snapTo(targetValue = alpha)
+ else -> _alpha.animateTo(targetValue = alpha)
+ }
+ }
+
+ suspend fun resetAlpha(
+ snap: Boolean = false,
+ ) {
+ when (snap) {
+ true -> _alpha.snapTo(targetValue = initialAlpha)
+ else -> _alpha.animateTo(targetValue = initialAlpha)
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Scene.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Scene.kt
new file mode 100644
index 0000000..9e2ed21
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Scene.kt
@@ -0,0 +1,278 @@
+package com.pixelized.desktop.lwa.ui.composable.scene
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.isAltPressed
+import androidx.compose.ui.input.pointer.isCtrlPressed
+import androidx.compose.ui.input.pointer.isPrimaryPressed
+import androidx.compose.ui.input.pointer.isTertiaryPressed
+import androidx.compose.ui.input.pointer.onPointerEvent
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import com.pixelized.desktop.lwa.ui.screen.campaign.LocalCampaignLayoutScope
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.ic_center_focus_weak_24dp
+import lwacharactersheet.composeapp.generated.resources.ic_zoom_in_map_24dp
+import lwacharactersheet.composeapp.generated.resources.ic_zoom_out_map_24dp
+import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
+import lwacharactersheet.composeapp.generated.resources.icon_d100
+import lwacharactersheet.composeapp.generated.resources.image_dahome_maps
+import lwacharactersheet.composeapp.generated.resources.image_dahome_regions
+import org.jetbrains.compose.resources.DrawableResource
+import org.jetbrains.compose.resources.imageResource
+import org.jetbrains.compose.resources.painterResource
+import kotlin.math.sign
+
+@Stable
+data class Scene(
+ val camera: Camera,
+ val layouts: List,
+ val fogOfWar: FogOfWar,
+) {
+ val size: IntSize = IntSize(
+ width = layouts.maxOf { it.size.width },
+ height = layouts.maxOf { it.size.height },
+ )
+}
+
+@Composable
+fun Scene(
+ modifier: Modifier,
+) {
+ val campaign = LocalCampaignLayoutScope.current
+ val scope = rememberCoroutineScope()
+ val scene = rememberScene(
+ camera = Camera(
+ initialZoom = 1f,
+ initialOffset = IntOffset(x = -150, y = -120),
+ ),
+ fogOfWar = FogOfWar(),
+ rememberLayoutFromResource(
+ resource = Res.drawable.image_dahome_maps,
+ ),
+ rememberLayoutFromResource(
+ resource = Res.drawable.image_dahome_regions,
+ ),
+ rememberLayoutFromResource(
+ resource = Res.drawable.icon_d100,
+ offset = IntOffset(x = 1740, y = 910),
+ ),
+ )
+ Box(
+ modifier = modifier
+ .graphicsLayer { clip = true }
+ .onCameraControl(scope = scope, scene = scene)
+ .drawScene(scene = scene)
+ .fogOfWar(scene = scene)
+ ) {
+ Column(
+ modifier = Modifier
+ .align(alignment = Alignment.BottomEnd)
+ .padding(end = campaign.rightPanel.value.width)
+ .padding(all = 8.dp)
+ ) {
+ IconButton(
+ onClick = {
+ scope.launch {
+ scene.camera.handleZoom(
+ zoomIn = true,
+ power = 0.3f,
+ )
+ }
+ }
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.ic_zoom_in_map_24dp),
+ contentDescription = null
+ )
+ }
+ IconButton(
+ onClick = {
+ scope.launch {
+ scene.camera.handleZoom(
+ zoomIn = false,
+ power = 0.3f,
+ )
+ }
+ }
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.ic_zoom_out_map_24dp),
+ contentDescription = null
+ )
+ }
+ IconButton(
+ onClick = {
+ scope.launch {
+ scene.camera.resetPosition()
+ }
+ scope.launch {
+ scene.camera.resetZoom()
+ }
+ }
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.ic_center_focus_weak_24dp),
+ contentDescription = null
+ )
+ }
+ IconButton(
+ onClick = {
+ scope.launch {
+ scene.layouts.getOrNull(1)?.let {
+ it.alpha(alpha = if (it.alpha == 0f) 1f else 0f)
+ }
+ }
+ }
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.ic_visibility_24dp),
+ contentDescription = null
+ )
+ }
+ }
+ }
+}
+
+@Composable
+@Stable
+fun rememberLayoutFromResource(
+ resource: DrawableResource,
+ offset: IntOffset = IntOffset.Zero,
+): Layout {
+ val texture = imageResource(
+ resource = resource,
+ )
+ return remember(resource) {
+ Layout(
+ texture = texture,
+ offset = offset,
+ )
+ }
+}
+
+@Composable
+@Stable
+fun rememberScene(
+ camera: Camera,
+ fogOfWar: FogOfWar,
+ vararg layouts: Layout,
+): Scene {
+ return remember {
+ Scene(
+ camera = camera,
+ layouts = layouts.toList(),
+ fogOfWar = fogOfWar,
+ )
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
+fun Modifier.onCameraControl(
+ scope: CoroutineScope,
+ scene: Scene,
+): Modifier {
+ val offsetDelta = CursorDelta()
+ return this
+ .onSizeChanged {
+ scene.camera.changeSizes(
+ sceneSize = scene.size,
+ cameraSize = it,
+ )
+ }
+ .onPointerEvent(PointerEventType.Move) { event: PointerEvent ->
+ scope.launch {
+ offsetDelta.handlePositionChange(
+ event = event,
+ ) { delta ->
+ when {
+ event.buttons.isTertiaryPressed || (event.keyboardModifiers.isCtrlPressed && event.buttons.isPrimaryPressed) -> scene.camera.handlePanning(
+ delta = delta,
+ snap = true,
+ )
+
+ event.keyboardModifiers.isAltPressed -> scene.camera.handleZoom(
+ zoomIn = delta.y.sign < 0f,
+ power = 0.025f,
+ snap = true,
+ )
+ }
+ }
+ }
+ }
+ .onPointerEvent(PointerEventType.Scroll) { event: PointerEvent ->
+ scope.launch {
+ scene.camera.handleZoom(
+ zoomIn = event.changes.first().scrollDelta.y.sign < 0f,
+ power = 0.15f,
+ snap = false,
+ )
+ }
+ }
+}
+
+fun Modifier.drawScene(
+ scene: Scene,
+): Modifier = this.drawWithCache {
+ onDrawBehind {
+ scene.layouts.forEach { layout ->
+ drawImage(
+ image = layout.texture,
+ srcOffset = scene.camera.offset - layout.offset,
+ srcSize = scene.camera.cameraSizeZoomed,
+ dstSize = scene.camera.cameraSize,
+ alpha = layout.alpha,
+ )
+ }
+ }
+}
+
+fun Modifier.fogOfWar(
+ scene: Scene,
+): Modifier = this.drawWithCache {
+ onDrawBehind {
+ drawRect(color = scene.fogOfWar.color)
+ }
+}
+
+private data class CursorDelta(
+ var lastDeltaTimestamp: Long = System.currentTimeMillis(),
+ var previousPosition: Offset = Offset.Zero,
+ var currentPosition: Offset = Offset.Zero,
+) {
+ suspend inline fun handlePositionChange(
+ event: PointerEvent,
+ delay: Float = 10f,
+ crossinline block: suspend (delta: Offset) -> Unit,
+ ) {
+ val currentTimestamp = System.currentTimeMillis()
+ if (currentTimestamp - lastDeltaTimestamp > delay) {
+ lastDeltaTimestamp = currentTimestamp
+ previousPosition = currentPosition
+ currentPosition = event.changes.first().position
+ block(currentPosition - previousPosition)
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/BasicTooltipLayout.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/BasicTooltipLayout.kt
index f5da12e..f5e6fee 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/BasicTooltipLayout.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/BasicTooltipLayout.kt
@@ -5,7 +5,7 @@ import androidx.compose.foundation.TooltipPlacement
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
@@ -56,7 +56,7 @@ fun BasicTooltipLayout(
tips = tooltip,
tooltip = {
BasicTooltip(
- modifier = Modifier.width(width = 448.dp),
+ modifier = Modifier.widthIn(max = 448.dp),
elevation = elevation,
tooltip = it,
)
@@ -72,7 +72,9 @@ private fun BasicTooltip(
tooltip: BasicTooltipUio,
) {
Surface(
- modifier = Modifier.padding(16.dp).then(other = modifier),
+ modifier = Modifier
+ .padding(16.dp)
+ .then(other = modifier),
color = MaterialTheme.colors.surface,
elevation = elevation,
shape = remember { RoundedCornerShape(4.dp) }
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt
index c18e99b..bd773ae 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
+import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -19,6 +20,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.isAltPressed
import androidx.compose.ui.input.key.isCtrlPressed
@@ -41,6 +43,7 @@ import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterShe
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
+import com.pixelized.desktop.lwa.ui.composable.scene.Scene
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToLevelScreen
import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlay
@@ -48,13 +51,14 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetai
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.DetailPanelUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc.NpcRibbon
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbon
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel
+import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbon
+import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.NpcRibbonViewModel
+import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbonViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChat
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbar
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel
+import com.pixelized.desktop.lwa.ui.theme.lwa
import kotlinx.coroutines.launch
import org.koin.compose.viewmodel.koinViewModel
@@ -66,6 +70,7 @@ val LocalCampaignLayoutScope = compositionLocalOf {
fun CampaignScreen(
playerRibbonViewModel: PlayerRibbonViewModel = koinViewModel(),
playerDetailViewModel: CharacterDetailPanelViewModel = koinViewModel(key = "player"),
+ npcRibbonViewModel: NpcRibbonViewModel = koinViewModel(),
npcDetailViewModel: CharacterDetailPanelViewModel = koinViewModel(key = "npc"),
characteristicDialogViewModel: CharacterSheetCharacteristicDialogViewModel = koinViewModel(),
dismissedViewModel: CharacterSheetDiminishedDialogViewModel = koinViewModel(),
@@ -95,7 +100,9 @@ fun CampaignScreen(
},
main = {
-
+ Scene(
+ modifier = Modifier.matchParentSize(),
+ )
},
chat = {
CampaignChat(
@@ -114,9 +121,10 @@ fun CampaignScreen(
}
},
leftPanel = {
- PlayerRibbon(
+ CharacterRibbon(
modifier = Modifier.fillMaxHeight(),
viewModel = playerRibbonViewModel,
+ layoutDirection = LayoutDirection.Ltr,
onCharacterLeftClick = {
scope.launch {
playerDetailViewModel.showCharacter(
@@ -139,8 +147,10 @@ fun CampaignScreen(
)
},
rightPanel = {
- NpcRibbon(
+ CharacterRibbon(
modifier = Modifier.fillMaxHeight(),
+ viewModel = npcRibbonViewModel,
+ layoutDirection = LayoutDirection.Rtl,
onCharacterLeftClick = {
scope.launch {
npcDetailViewModel.showCharacter(
@@ -166,11 +176,11 @@ fun CampaignScreen(
CharacterDetailPanel(
modifier = Modifier
.padding(all = 8.dp)
+ .padding(start = MaterialTheme.lwa.size.portrait.minimized.width + 8.dp)
.width(width = 128.dp * 4)
.fillMaxHeight(),
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Ltr),
- blurController = blurController,
- detailPanelViewModel = npcDetailViewModel,
+ detailPanelViewModel = playerDetailViewModel,
characterDiminishedViewModel = dismissedViewModel,
characteristicDialogViewModel = characteristicDialogViewModel,
alterationViewModel = alterationViewModel,
@@ -183,11 +193,11 @@ fun CampaignScreen(
CharacterDetailPanel(
modifier = Modifier
.padding(all = 8.dp)
+ .padding(end = MaterialTheme.lwa.size.portrait.minimized.width + 8.dp)
.width(width = 128.dp * 4)
.fillMaxHeight(),
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Rtl),
- blurController = blurController,
- detailPanelViewModel = playerDetailViewModel,
+ detailPanelViewModel = npcDetailViewModel,
characterDiminishedViewModel = dismissedViewModel,
characteristicDialogViewModel = characteristicDialogViewModel,
alterationViewModel = alterationViewModel,
@@ -206,7 +216,7 @@ fun CampaignScreen(
characteristicDialogViewModel.changeSubCharacteristic(
characterSheetId = dialog.characterSheetId,
characteristic = dialog.characteristic,
- useArmor= dialog.enableArmor?.checked?.value == true,
+ useArmor = dialog.enableArmor?.checked?.value == true,
value = dialog.value.valueFlow.value.toIntOrNull() ?: 0,
)
characteristicDialogViewModel.hideSubCharacteristicDialog()
@@ -308,6 +318,7 @@ private fun CampaignLayout(
) {
val density = LocalDensity.current
+ val mainState = remember { mutableStateOf(DpSize.Unspecified) }
val leftOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
val leftPanelState = remember { mutableStateOf(DpSize.Unspecified) }
val rightOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
@@ -315,6 +326,7 @@ private fun CampaignLayout(
val chatOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
val scope = remember {
CampaignLayoutScope(
+ main = mainState,
leftOverlay = leftOverlayState,
leftPanel = leftPanelState,
rightOverlay = rightOverlayState,
@@ -335,14 +347,18 @@ private fun CampaignLayout(
) {
Box(
modifier = Modifier
- .align(alignment = Alignment.Center)
- .fillMaxSize(),
+ .onSizeChanged { mainState.value = it.toDp(density) }
+ .matchParentSize(),
) {
main()
}
Box(
modifier = Modifier
- .align(alignment = Alignment.BottomEnd)
+ .align(alignment = Alignment.BottomStart)
+ .padding(
+ start = MaterialTheme.lwa.size.portrait.minimized.width * 2 + 20.dp,
+ end = MaterialTheme.lwa.size.portrait.minimized.width * 2 + 20.dp + 56.dp,
+ )
.onSizeChanged { chatOverlayState.value = it.toDp(density) },
) {
chat()
@@ -394,24 +410,31 @@ private fun CampaignKeyHandler(
onPlayerNumber: (index: Int) -> Unit,
onAltPLayerNumber: (index: Int) -> Unit,
) {
+ fun KeyEvent.callback(index: Int) {
+ if (isAltPressed) onAltPLayerNumber(index) else onPlayerNumber(index)
+ }
KeyHandler {
- if (it.type != KeyEventType.KeyDown) return@KeyHandler false
+ if (it.type != KeyEventType.KeyDown) {
+ return@KeyHandler false
+ }
if (it.key == Key.Escape) {
onDismissRequest()
return@KeyHandler true
}
- if (it.isCtrlPressed.not()) return@KeyHandler false
+ if (it.isCtrlPressed.not()) {
+ return@KeyHandler false
+ }
when (it.key) {
Key.Escape -> onDismissRequest()
- Key.One, Key.NumPad1 -> if (it.isAltPressed) onAltPLayerNumber(0) else onPlayerNumber(0)
- Key.Two, Key.NumPad2 -> if (it.isAltPressed) onAltPLayerNumber(1) else onPlayerNumber(1)
- Key.Three, Key.NumPad3 -> if (it.isAltPressed) onAltPLayerNumber(2) else onPlayerNumber(2)
- Key.Four, Key.NumPad4 -> if (it.isAltPressed) onAltPLayerNumber(3) else onPlayerNumber(3)
- Key.Five, Key.NumPad5 -> if (it.isAltPressed) onAltPLayerNumber(4) else onPlayerNumber(4)
- Key.Six, Key.NumPad6 -> if (it.isAltPressed) onAltPLayerNumber(5) else onPlayerNumber(5)
- Key.Seven, Key.NumPad7 -> if (it.isAltPressed) onAltPLayerNumber(6) else onPlayerNumber(6)
- Key.Eight, Key.NumPad8 -> if (it.isAltPressed) onAltPLayerNumber(7) else onPlayerNumber(7)
- Key.Nine, Key.NumPad9 -> if (it.isAltPressed) onAltPLayerNumber(8) else onPlayerNumber(8)
+ Key.One, Key.NumPad1 -> it.callback(index = 0)
+ Key.Two, Key.NumPad2 -> it.callback(index = 1)
+ Key.Three, Key.NumPad3 -> it.callback(index = 2)
+ Key.Four, Key.NumPad4 -> it.callback(index = 3)
+ Key.Five, Key.NumPad5 -> it.callback(index = 4)
+ Key.Six, Key.NumPad6 -> it.callback(index = 5)
+ Key.Seven, Key.NumPad7 -> it.callback(index = 6)
+ Key.Eight, Key.NumPad8 -> it.callback(index = 7)
+ Key.Nine, Key.NumPad9 -> it.callback(index = 8)
else -> return@KeyHandler false
}
return@KeyHandler true
@@ -427,6 +450,7 @@ private fun IntSize.toDp(density: Density) = with(density) {
@Stable
data class CampaignLayoutScope(
+ val main: State,
val leftOverlay: State,
val leftPanel: State,
val rightOverlay: State,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailPanel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailPanel.kt
index ccf2552..6119193 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailPanel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailPanel.kt
@@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
@@ -31,7 +30,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.LocalRollHostState
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
import com.pixelized.desktop.lwa.ui.composable.character.alterteration.CharacterSheetAlterationDialogViewModel
@@ -74,7 +72,6 @@ enum class DetailPanelUio {
@Composable
fun CharacterDetailPanel(
modifier: Modifier = Modifier,
- blurController: BlurContentController,
transitionSpec: AnimatedContentTransitionScope.() -> ContentTransform = rememberTransitionAnimation(),
detailPanelViewModel: CharacterDetailPanelViewModel,
characteristicDialogViewModel: CharacterSheetCharacteristicDialogViewModel,
@@ -121,12 +118,10 @@ fun CharacterDetailPanel(
}
},
onAlteration = {
- blurController.show()
alterationViewModel.show(characterSheetId = it)
},
onDiminished = {
scope.launch {
- blurController.show()
characterDiminishedViewModel.showDiminishedDialog(
characterSheetId = it
)
@@ -134,7 +129,6 @@ fun CharacterDetailPanel(
},
onHp = {
scope.launch {
- blurController.show()
characteristicDialogViewModel.showSubCharacteristicDialog(
characterSheetId = it,
characteristic = CharacterSheetCharacteristicDialogUio.Characteristic.Damage,
@@ -143,7 +137,6 @@ fun CharacterDetailPanel(
},
onPp = {
scope.launch {
- blurController.show()
characteristicDialogViewModel.showSubCharacteristicDialog(
characterSheetId = it,
characteristic = CharacterSheetCharacteristicDialogUio.Characteristic.Fatigue,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt
index 714593f..0221bef 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt
@@ -107,7 +107,6 @@ fun CharacterDetailInventory(
itemDetailDialogViewModel: ItemDetailDialogViewModel = koinViewModel(),
inventory: State,
) {
- val blur = LocalBlurController.current
val focus = LocalFocusManager.current
val scope = rememberCoroutineScope()
@@ -120,14 +119,12 @@ fun CharacterDetailInventory(
spacing = spacing,
inventory = unWrap,
onPurse = {
- blur.show()
purseViewModel.showPurseDialog(
characterSheetId = it,
)
focus.clearFocus(force = true)
},
onItem = { item ->
- blur.show()
itemDetailDialogViewModel.showItemDialog(
characterSheetId = item.characterSheetId,
inventoryId = item.inventoryId,
@@ -136,7 +133,6 @@ fun CharacterDetailInventory(
focus.clearFocus(force = true)
},
onAddItem = {
- blur.show()
inventoryDialogViewModel.showInventoryDialog(
characterSheetId = it,
)
@@ -166,7 +162,6 @@ fun CharacterDetailInventory(
PurseDialog(
dialog = purseViewModel.purseDialog.collectAsState(),
onDismissRequest = {
- blur.hide()
purseViewModel.hidePurseDialog()
},
onSwapSign = {
@@ -175,7 +170,6 @@ fun CharacterDetailInventory(
onConfirm = {
scope.launch {
if (purseViewModel.confirmPurse(dialog = it)) {
- blur.hide()
purseViewModel.hidePurseDialog()
}
}
@@ -185,11 +179,9 @@ fun CharacterDetailInventory(
InventoryDialog(
dialog = inventoryDialogViewModel.inventoryDialog.collectAsState(),
onDismissRequest = {
- blur.hide()
inventoryDialogViewModel.hideInventoryDialog()
},
onItem = { dialog, itemId ->
- blur.show()
itemDetailDialogViewModel.showItemDialog(
characterSheetId = dialog.characterSheetId,
inventoryId = null,
@@ -201,7 +193,6 @@ fun CharacterDetailInventory(
ItemDetailDialog(
dialog = itemDetailDialogViewModel.itemDialog.collectAsState(),
onDismissRequest = {
- blur.hide()
itemDetailDialogViewModel.hideItemDialog()
},
onAddItem = { dialog ->
@@ -210,7 +201,6 @@ fun CharacterDetailInventory(
dialog = dialog,
)
if (result) {
- blur.hide()
itemDetailDialogViewModel.hideItemDialog()
}
}
@@ -221,7 +211,6 @@ fun CharacterDetailInventory(
dialog = dialog,
)
if (result) {
- blur.hide()
itemDetailDialogViewModel.hideItemDialog()
}
}
@@ -233,7 +222,6 @@ fun CharacterDetailInventory(
inventoryId = dialog.inventoryId,
)
if (result) {
- blur.hide()
itemDetailDialogViewModel.hideItemDialog()
}
}
@@ -245,7 +233,6 @@ fun CharacterDetailInventory(
inventoryId = dialog.inventoryId,
)
if (result) {
- blur.hide()
itemDetailDialogViewModel.hideItemDialog()
}
}
@@ -257,7 +244,6 @@ fun CharacterDetailInventory(
inventoryId = dialog.inventoryId,
)
if (result) {
- blur.hide()
itemDetailDialogViewModel.hideItemDialog()
}
}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/CharacterRibbon.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/CharacterRibbon.kt
new file mode 100644
index 0000000..c67da46
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/CharacterRibbon.kt
@@ -0,0 +1,70 @@
+package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortrait
+import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRoll
+import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlteration
+
+@Composable
+fun CharacterRibbon(
+ modifier: Modifier = Modifier,
+ layoutDirection: LayoutDirection,
+ viewModel: CharacterRibbonViewModel,
+ padding: PaddingValues = PaddingValues(all = 8.dp),
+ onCharacterLeftClick: (characterSheetId: String) -> Unit,
+ onCharacterRightClick: (characterSheetId: String) -> Unit,
+ onLevelUp: (characterSheetId: String) -> Unit,
+) {
+ val characters = viewModel.characters.collectAsState()
+
+ CompositionLocalProvider(
+ LocalLayoutDirection provides layoutDirection
+ ) {
+ LazyColumn(
+ modifier = modifier,
+ contentPadding = padding,
+ verticalArrangement = Arrangement.spacedBy(space = 8.dp)
+ ) {
+ items(
+ items = characters.value,
+ key = { it.characterSheetId },
+ ) {
+ Row(
+ modifier = Modifier
+ .animateItem()
+ .graphicsLayer { if (it.hideOverruled) this.alpha = 0.3f },
+ horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
+ ) {
+ Box {
+ CharacterRibbonPortrait(
+ character = it.portrait,
+ onCharacterLeftClick = { onCharacterLeftClick(it.characterSheetId) },
+ onCharacterRightClick = { onCharacterRightClick(it.characterSheetId) },
+ onLevelUp = { onLevelUp(it.characterSheetId) },
+ )
+ CharacterRibbonRoll(
+ value = viewModel.roll(characterSheetId = it.characterSheetId).value,
+ )
+ }
+ CharacterRibbonAlteration(
+ status = it.status,
+ direction = LayoutDirection.Ltr,
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbonViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/NpcRibbonViewModel.kt
similarity index 91%
rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbonViewModel.kt
rename to composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/NpcRibbonViewModel.kt
index 3dd6c5b..e029293 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbonViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/NpcRibbonViewModel.kt
@@ -1,4 +1,4 @@
-package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc
+package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
@@ -6,8 +6,6 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetReposit
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.repository.settings.model.Settings
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonViewModel
import com.pixelized.shared.lwa.model.campaign.Campaign
class NpcRibbonViewModel(
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbonViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt
similarity index 90%
rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbonViewModel.kt
rename to composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt
index e80b095..df81d36 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbonViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt
@@ -1,4 +1,4 @@
-package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player
+package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
@@ -6,8 +6,6 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetReposit
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.repository.settings.model.Settings
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonViewModel
import com.pixelized.shared.lwa.model.campaign.Campaign
class PlayerRibbonViewModel(
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonAlteration.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonAlteration.kt
index 838967d..bdb8729 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonAlteration.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonAlteration.kt
@@ -32,6 +32,7 @@ import com.pixelized.desktop.lwa.ui.theme.lwa
@Stable
data class CharacterRibbonAlterationUio(
+
val icon: String,
val tooltips: BasicTooltipUio?,
)
@@ -44,60 +45,56 @@ fun CharacterRibbonAlteration(
direction: LayoutDirection,
status: List>,
) {
- val currentDirection: LayoutDirection = LocalLayoutDirection.current
+ val currentDirection = LocalLayoutDirection.current
- CompositionLocalProvider(
- LocalLayoutDirection provides direction
+ Row(
+ modifier = Modifier
+ .animateContentSize()
+ .size(size = size)
+ .then(other = modifier),
+ horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
- Row(
- modifier = Modifier
- .animateContentSize()
- .size(size = size)
- .then(other = modifier),
- horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
- ) {
- CompositionLocalProvider(
- LocalLayoutDirection provides currentDirection
+ status.forEach { columns ->
+ Column(
+ modifier = Modifier.animateContentSize(),
+ verticalArrangement = Arrangement.spacedBy(space = 2.dp),
) {
- status.forEach { columns ->
- Column(
- modifier = Modifier.animateContentSize(),
- verticalArrangement = Arrangement.spacedBy(space = 2.dp),
- ) {
- columns.forEach {
- BasicTooltipLayout(
- delayMillis = 0,
- tooltip = it.tooltips,
- tooltipPlacement = remember(currentDirection) {
- TooltipPlacement.ComponentRect(
- anchor = when (direction) {
- LayoutDirection.Ltr -> Alignment.TopStart
- LayoutDirection.Rtl -> Alignment.TopEnd
- },
- alignment = when (direction) {
- LayoutDirection.Ltr -> Alignment.BottomEnd
- LayoutDirection.Rtl -> Alignment.BottomStart
- },
+ CompositionLocalProvider(
+ LocalLayoutDirection provides LayoutDirection.Ltr
+ ) {
+ columns.forEach {
+ BasicTooltipLayout(
+ delayMillis = 0,
+ tooltip = it.tooltips,
+ tooltipPlacement = remember(currentDirection) {
+ TooltipPlacement.ComponentRect(
+ anchor = when (direction) {
+ LayoutDirection.Ltr -> Alignment.CenterEnd
+ LayoutDirection.Rtl -> Alignment.CenterStart
+ },
+ alignment = when (direction) {
+ LayoutDirection.Ltr -> Alignment.CenterEnd
+ LayoutDirection.Rtl -> Alignment.CenterStart
+ },
+ )
+ },
+ content = {
+ AnimatedContent(
+ targetState = it.icon,
+ transitionSpec = { fadeIn() togetherWith fadeOut() },
+ ) { icon ->
+ AsyncImage(
+ modifier = Modifier.size(24.dp),
+ model = ImageRequest.Builder(context = PlatformContext.INSTANCE)
+ .data(data = icon)
+ .size(size = 48)
+ .build(),
+ filterQuality = FilterQuality.High,
+ contentDescription = null,
)
- },
- content = {
- AnimatedContent(
- targetState = it.icon,
- transitionSpec = { fadeIn() togetherWith fadeOut() },
- ) { icon ->
- AsyncImage(
- modifier = Modifier.size(24.dp),
- model = ImageRequest.Builder(context = PlatformContext.INSTANCE)
- .data(data = icon)
- .size(size = 48)
- .build(),
- filterQuality = FilterQuality.High,
- contentDescription = null,
- )
- }
}
- )
- }
+ }
+ )
}
}
}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbon.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbon.kt
deleted file mode 100644
index a9fbfcb..0000000
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbon.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortrait
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRoll
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlteration
-import org.koin.compose.viewmodel.koinViewModel
-
-@Composable
-fun NpcRibbon(
- modifier: Modifier = Modifier,
- viewModel: NpcRibbonViewModel = koinViewModel(),
- padding: PaddingValues = PaddingValues(all = 8.dp),
- onCharacterLeftClick: (characterSheetId: String) -> Unit,
- onCharacterRightClick: (characterSheetId: String) -> Unit,
- onLevelUp: (characterSheetId: String) -> Unit,
-) {
- val characters = viewModel.characters.collectAsState()
-
- LazyColumn(
- modifier = modifier,
- contentPadding = padding,
- verticalArrangement = Arrangement.spacedBy(space = 8.dp)
- ) {
- items(
- items = characters.value,
- key = { it.characterSheetId },
- ) {
- Row(
- modifier = Modifier
- .animateItem()
- .graphicsLayer { if (it.hideOverruled) this.alpha = 0.3f },
- horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
- ) {
- CharacterRibbonAlteration(
- status = it.status,
- direction = LayoutDirection.Rtl,
- )
- Box {
- CharacterRibbonPortrait(
- character = it.portrait,
- onCharacterLeftClick = { onCharacterLeftClick(it.characterSheetId) },
- onCharacterRightClick = { onCharacterRightClick(it.characterSheetId) },
- onLevelUp = { onLevelUp(it.characterSheetId) },
- )
- CharacterRibbonRoll(
- value = viewModel.roll(characterSheetId = it.characterSheetId).value,
- )
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbon.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbon.kt
deleted file mode 100644
index 37554ad..0000000
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbon.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortrait
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRoll
-import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlteration
-import org.koin.compose.viewmodel.koinViewModel
-
-@Composable
-fun PlayerRibbon(
- modifier: Modifier = Modifier,
- viewModel: PlayerRibbonViewModel = koinViewModel(),
- padding: PaddingValues = PaddingValues(all = 8.dp),
- onCharacterLeftClick: (characterSheetId: String) -> Unit,
- onCharacterRightClick: (characterSheetId: String) -> Unit,
- onLevelUp: (characterSheetId: String) -> Unit,
-) {
- val characters = viewModel.characters.collectAsState()
-
- LazyColumn(
- modifier = modifier,
- contentPadding = padding,
- verticalArrangement = Arrangement.spacedBy(space = 8.dp)
- ) {
- items(
- items = characters.value,
- key = { it.characterSheetId },
- ) {
- Row(
- modifier = Modifier
- .animateItem()
- .graphicsLayer { if (it.hideOverruled) this.alpha = 0.3f },
- horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
- ) {
- Box {
- CharacterRibbonPortrait(
- character = it.portrait,
- onCharacterLeftClick = { onCharacterLeftClick(it.characterSheetId) },
- onCharacterRightClick = { onCharacterRightClick(it.characterSheetId) },
- onLevelUp = { onLevelUp(it.characterSheetId) },
- )
- CharacterRibbonRoll(
- value = viewModel.roll(characterSheetId = it.characterSheetId).value,
- )
- }
- CharacterRibbonAlteration(
- status = it.status,
- direction = LayoutDirection.Ltr,
- )
- }
- }
- }
-}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChat.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChat.kt
index c3a1453..9af413a 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChat.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChat.kt
@@ -1,18 +1,28 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.text
+import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.expandIn
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.shrinkOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
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.fillMaxSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -21,13 +31,10 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.pointer.PointerEventType
-import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
@@ -47,21 +54,21 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessag
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.TextMessage
import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.desktop.lwa.usecase.SettingsUseCase
-import kotlinx.coroutines.launch
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.ic_more_down_24dp
+import org.jetbrains.compose.resources.painterResource
import org.koin.compose.viewmodel.koinViewModel
@Stable
data class ChatSettingsUio(
- val autoShowChat: Boolean,
- val autoScrollChat: Boolean,
- val autoHideChat: Boolean,
+ val show: Boolean,
+ val autoScroll: Boolean,
) {
companion object {
fun default() = with(SettingsUseCase().defaultSettings()) {
ChatSettingsUio(
- autoShowChat = chat.autoShowChat,
- autoScrollChat = chat.autoScrollChat,
- autoHideChat = chat.autoHideChat,
+ show = chat.showChat,
+ autoScroll = chat.autoScrollChat,
)
}
}
@@ -73,9 +80,9 @@ fun CampaignChat(
modifier: Modifier = Modifier,
chatViewModel: CampaignChatViewModel = koinViewModel(),
) {
- val scope = rememberCoroutineScope()
+
val lazyState = rememberLazyListState()
- val animatedChatWidth = rememberAnimatedChatWidth()
+ val campaignLayoutScope = LocalCampaignLayoutScope.current
val colorScheme = MaterialTheme.lwa.colorScheme
val messages = chatViewModel.messages.collectAsState()
val settings = chatViewModel.settings.collectAsState()
@@ -84,54 +91,63 @@ fun CampaignChat(
lazyState = lazyState,
messages = messages,
settings = settings,
- displayChat = chatViewModel::displayChat,
- hideChat = chatViewModel::hideChat,
)
- Box(
- modifier = modifier
- .size(
- width = animatedChatWidth.value,
- height = MaterialTheme.lwa.size.portrait.minimized.height * 2 + 8.dp,
- )
- .graphicsLayer {
- alpha = chatViewModel.chatAnimatedVisibility.value
- }
- .background(
- shape = remember { RoundedCornerShape(8.dp) },
- color = remember { colorScheme.elevated.base1dp.copy(alpha = 0.5f) },
- )
- .onPointerEvent(eventType = PointerEventType.Enter) {
- scope.launch { chatViewModel.displayChat() }
- }
- .onPointerEvent(eventType = PointerEventType.Exit) {
- if (settings.value.autoHideChat) {
- scope.launch { chatViewModel.hideChat() }
- }
- },
+ Row(
+ modifier = modifier.background(
+ shape = remember { RoundedCornerShape(8.dp) },
+ color = remember { colorScheme.elevated.base1dp.copy(alpha = 0.5f) },
+ ),
) {
- LazyColumn(
- modifier = Modifier.fillMaxSize(),
- state = lazyState,
- verticalArrangement = Arrangement.spacedBy(
- space = 4.dp,
- alignment = Alignment.Bottom,
- ),
- contentPadding = remember { PaddingValues(all = 8.dp) },
+ AnimatedVisibility(
+ visible = settings.value.show,
+ enter = fadeIn() + expandIn(),
+ exit = fadeOut() + shrinkOut(),
) {
- items(
- items = messages.value,
- key = { it.id },
- contentType = { it.javaClass.simpleName }
+ LazyColumn(
+ modifier = Modifier
+ .width(width = campaignLayoutScope.chatOverlay.value.width - (32.dp + 8.dp))
+ .heightIn(min = MaterialTheme.lwa.size.portrait.minimized.height * 2 + 8.dp),
+ state = lazyState,
+ verticalArrangement = Arrangement.spacedBy(
+ space = 4.dp,
+ alignment = Alignment.Bottom,
+ ),
+ contentPadding = remember { PaddingValues(all = 8.dp) },
) {
- when (it) {
- is RollTextMessageUio -> RollTextMessage(message = it)
- is PurseTextMessageUio -> PurseTextMessage(message = it)
- is DiminishedTextMessageUio -> DiminishedTextMessage(message = it)
- is CharacteristicTextMessageUio -> CharacteristicTextMessage(message = it)
+ items(
+ items = messages.value,
+ key = { it.id },
+ contentType = { it.javaClass.simpleName }
+ ) {
+ when (it) {
+ is RollTextMessageUio -> RollTextMessage(message = it)
+ is PurseTextMessageUio -> PurseTextMessage(message = it)
+ is DiminishedTextMessageUio -> DiminishedTextMessage(message = it)
+ is CharacteristicTextMessageUio -> CharacteristicTextMessage(message = it)
+ }
}
}
}
+ Column {
+ IconButton(
+ modifier = Modifier.size(size = 32.dp),
+ onClick = chatViewModel::toggleChat
+ ) {
+ val rotation = animateFloatAsState(
+ targetValue = if (settings.value.show) 0f else 180f,
+ )
+ Icon(
+ modifier = Modifier
+ .size(size = 16.dp)
+ .graphicsLayer {
+ this.rotationZ = rotation.value
+ },
+ painter = painterResource(Res.drawable.ic_more_down_24dp),
+ contentDescription = null,
+ )
+ }
+ }
}
}
@@ -140,24 +156,16 @@ private fun ChatScrollDownEffect(
lazyState: LazyListState,
messages: State>,
settings: State,
- displayChat: suspend () -> Unit,
- hideChat: suspend () -> Unit,
) {
LaunchedEffect(
key1 = messages.value.lastOrNull()?.id,
) {
if (messages.value.isNotEmpty()) {
- if (settings.value.autoShowChat) {
- displayChat()
- }
- if (settings.value.autoScrollChat) {
+ if (settings.value.autoScroll) {
lazyState.animateScrollToItem(
index = messages.value.lastIndex + 1,
)
}
- if (settings.value.autoHideChat) {
- hideChat()
- }
}
}
}
@@ -167,19 +175,26 @@ private fun ChatScrollDownEffect(
private fun rememberAnimatedChatWidth(
campaignScreenScope: CampaignLayoutScope = LocalCampaignLayoutScope.current,
windowsState: WindowState = LocalWindowState.current,
+ settings: State,
): State {
val chatWidth = remember(windowsState, campaignScreenScope) {
derivedStateOf {
- val minChatWidth = 64.dp * 8
- val maxChatWidth = 64.dp * 12
- val windowWidth = windowsState.size.width
- if (windowWidth != Dp.Unspecified) {
- val width = windowWidth - campaignScreenScope.leftPanel.value.width - 16.dp
- min(max(width, minChatWidth), maxChatWidth)
+ if (settings.value.show) {
+ val minChatWidth = 64.dp * 8
+ val maxChatWidth = 64.dp * 12
+ val windowWidth = windowsState.size.width
+ if (windowWidth != Dp.Unspecified) {
+ val width = windowWidth - campaignScreenScope.leftPanel.value.width - 16.dp
+ min(max(width, minChatWidth), maxChatWidth)
+ } else {
+ minChatWidth
+ }
} else {
- minChatWidth
+ 0.dp
}
}
}
- return animateDpAsState(targetValue = chatWidth.value)
+ return animateDpAsState(
+ targetValue = chatWidth.value,
+ )
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChatViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChatViewModel.kt
index 21baec1..a2fab42 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChatViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChatViewModel.kt
@@ -1,7 +1,5 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.text
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.tween
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
@@ -13,22 +11,20 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
class CampaignChatViewModel(
private val settingsRepository: SettingsRepository,
- private val campaignRepository: CampaignRepository,
+ campaignRepository: CampaignRepository,
networkRepository: NetworkRepository,
textMessageFactory: TextMessageFactory,
) : ViewModel() {
- val chatAnimatedVisibility = Animatable(0f)
-
val settings = settingsRepository.settingsFlow().map {
ChatSettingsUio(
- autoShowChat = it.chat.autoShowChat,
- autoScrollChat = it.chat.autoScrollChat,
- autoHideChat = it.chat.autoHideChat,
+ show = it.chat.showChat,
+ autoScroll = it.chat.autoScrollChat,
)
}.stateIn(
scope = viewModelScope,
@@ -59,20 +55,14 @@ class CampaignChatViewModel(
initialValue = emptyList(),
)
- suspend fun displayChat() {
- chatAnimatedVisibility.animateTo(
- targetValue = 1f,
- )
- }
-
- suspend fun hideChat() {
- val settings = settingsRepository.settingsFlow().value
- chatAnimatedVisibility.animateTo(
- targetValue = 0f,
- animationSpec = tween(
- durationMillis = 2000,
- delayMillis = settings.chat.autoHideDelay * 1000,
+ fun toggleChat() {
+ viewModelScope.launch {
+ val settings = settingsRepository.settingsFlow().value
+ settingsRepository.update(
+ settings = settings.copy(
+ chat = settings.chat.copy(showChat = settings.chat.showChat.not())
+ )
)
- )
+ }
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionPage.kt
index e7a5dcb..74860c8 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionPage.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionPage.kt
@@ -10,12 +10,13 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler
+import com.pixelized.desktop.lwa.ui.composable.confirmation.ConfirmationDialog
import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_camping_24dp
@@ -36,7 +37,8 @@ fun GMActionPage(
) {
val scope = rememberCoroutineScope()
val scroll = rememberScrollState()
- val actions = viewModel.actions.collectAsState()
+ val actions = viewModel.actions.collectAsStateWithLifecycle()
+ val validationDialog = viewModel.validationDialog.collectAsStateWithLifecycle()
GMActionContent(
actions = actions,
@@ -66,6 +68,10 @@ fun GMActionPage(
ErrorSnackHandler(
error = viewModel.error,
)
+
+ ConfirmationDialog(
+ dialog = validationDialog,
+ )
}
@Composable
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionUseCase.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionUseCase.kt
new file mode 100644
index 0000000..895f9a0
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionUseCase.kt
@@ -0,0 +1,58 @@
+package com.pixelized.desktop.lwa.ui.screen.gamemaster.action
+
+import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
+import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
+import com.pixelized.desktop.lwa.repository.network.NetworkRepository
+import com.pixelized.shared.lwa.protocol.websocket.GameAdminEvent
+import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
+
+class GMActionUseCase(
+ private val characterRepository: CharacterSheetRepository,
+ private val networkRepository: NetworkRepository,
+ private val campaignRepository: CampaignRepository,
+) {
+ suspend fun invalidateServerCache() {
+ networkRepository.share(
+ GameAdminEvent.ServerSynchronization(
+ timestamp = System.currentTimeMillis(),
+ )
+ )
+ }
+
+ suspend fun healPlayerParty() {
+ campaignRepository.campaignFlow().value.characters.forEach { characterSheetId ->
+ val sheet = characterRepository.characterDetail(
+ characterSheetId = characterSheetId,
+ ) ?: return@forEach
+
+ val updated = sheet.copy(
+ damage = 0,
+ fatigue = 0,
+ diminished = 0,
+ )
+
+ if (sheet != updated) {
+ characterRepository.updateCharacter(
+ sheet = updated,
+ create = false,
+ )
+ }
+ }
+ }
+
+ suspend fun toggleNpcVisibility() {
+ networkRepository.share(
+ GameMasterEvent.ToggleNpc(
+ timestamp = System.currentTimeMillis(),
+ )
+ )
+ }
+
+ suspend fun togglePlayerVisibility() {
+ networkRepository.share(
+ GameMasterEvent.TogglePlayer(
+ timestamp = System.currentTimeMillis(),
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionViewModel.kt
index 3a980c3..c80f8be 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionViewModel.kt
@@ -3,23 +3,36 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster.action
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
-import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
-import com.pixelized.desktop.lwa.repository.network.NetworkRepository
+import com.pixelized.desktop.lwa.ui.composable.confirmation.ConfirmationDialogUio
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
-import com.pixelized.shared.lwa.protocol.websocket.GameAdminEvent
-import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.game_master__actions__hide_npc__description
+import lwacharactersheet.composeapp.generated.resources.game_master__actions__hide_npc__title
+import lwacharactersheet.composeapp.generated.resources.game_master__actions__hide_player__description
+import lwacharactersheet.composeapp.generated.resources.game_master__actions__hide_player__title
+import lwacharactersheet.composeapp.generated.resources.game_master__actions__on_server_sync__description
+import lwacharactersheet.composeapp.generated.resources.game_master__actions__on_server_sync__title
+import lwacharactersheet.composeapp.generated.resources.game_master__actions__party_heal__description
+import lwacharactersheet.composeapp.generated.resources.game_master__actions__party_heal__title
+import lwacharactersheet.composeapp.generated.resources.game_master__actions__show_npc__description
+import lwacharactersheet.composeapp.generated.resources.game_master__actions__show_npc__title
+import lwacharactersheet.composeapp.generated.resources.game_master__actions__show_player__description
+import lwacharactersheet.composeapp.generated.resources.game_master__actions__show_player__title
+import org.jetbrains.compose.resources.StringResource
+import org.jetbrains.compose.resources.getString
class GMActionViewModel(
- private val characterRepository: CharacterSheetRepository,
- private val networkRepository: NetworkRepository,
- private val campaignRepository: CampaignRepository,
+ private val actionUseCase: GMActionUseCase,
+ campaignRepository: CampaignRepository,
) : ViewModel() {
private val _error = MutableSharedFlow()
@@ -39,68 +52,102 @@ class GMActionViewModel(
initialValue = null,
)
+ private val _validationDialog = MutableStateFlow(null)
+ val validationDialog: StateFlow = _validationDialog
+
suspend fun onServerSync() {
- try {
- networkRepository.share(
- GameAdminEvent.ServerSynchronization(
- timestamp = System.currentTimeMillis(),
- )
- )
- } catch (exception: Exception) {
- val message = ErrorSnackUio.from(exception = exception)
- _error.emit(message)
- }
- }
-
- suspend fun onPartyHeal() {
- campaignRepository.campaignFlow().value.characters.forEach { characterSheetId ->
- val sheet = characterRepository.characterDetail(
- characterSheetId = characterSheetId,
- ) ?: return@forEach
-
- val updated = sheet.copy(
- damage = 0,
- fatigue = 0,
- diminished = 0,
- )
-
- if (sheet != updated) {
+ showConfirmationDialog(
+ title = Res.string.game_master__actions__on_server_sync__title,
+ description = Res.string.game_master__actions__on_server_sync__description,
+ onConfirmationRequest = {
try {
- characterRepository.updateCharacter(
- sheet = updated,
- create = false,
- )
+ actionUseCase.invalidateServerCache()
} catch (exception: Exception) {
val message = ErrorSnackUio.from(exception = exception)
_error.emit(message)
}
+ },
+ onDismissRequest = {
+ _validationDialog.value = null
}
- }
+ )
}
- suspend fun onNpcVisibility() {
- try {
- networkRepository.share(
- GameMasterEvent.ToggleNpc(
- timestamp = System.currentTimeMillis(),
- )
- )
- } catch (exception: Exception) {
- val message = ErrorSnackUio.from(exception = exception)
- _error.emit(message)
- }
+ suspend fun onPartyHeal() {
+ showConfirmationDialog(
+ title = Res.string.game_master__actions__party_heal__title,
+ description = Res.string.game_master__actions__party_heal__description,
+ onConfirmationRequest = {
+ try {
+ actionUseCase.healPlayerParty()
+ } catch (exception: Exception) {
+ val message = ErrorSnackUio.from(exception = exception)
+ _error.emit(message)
+ }
+ },
+ )
}
suspend fun onPlayerVisibility() {
- try {
- networkRepository.share(
- GameMasterEvent.TogglePlayer(
- timestamp = System.currentTimeMillis(),
- )
- )
- } catch (exception: Exception) {
- val message = ErrorSnackUio.from(exception = exception)
- _error.emit(message)
- }
+ showConfirmationDialog(
+ title = when (actions.value?.party) {
+ true -> Res.string.game_master__actions__hide_player__title
+ else -> Res.string.game_master__actions__show_player__title
+ },
+ description = when (actions.value?.party) {
+ true -> Res.string.game_master__actions__hide_player__description
+ else -> Res.string.game_master__actions__show_player__description
+ },
+ onConfirmationRequest = {
+ try {
+ actionUseCase.togglePlayerVisibility()
+ } catch (exception: Exception) {
+ val message = ErrorSnackUio.from(exception = exception)
+ _error.emit(message)
+ }
+ },
+ )
+ }
+
+ suspend fun onNpcVisibility() {
+ showConfirmationDialog(
+ title = when (actions.value?.npc) {
+ true -> Res.string.game_master__actions__hide_npc__title
+ else -> Res.string.game_master__actions__show_npc__title
+ },
+ description = when (actions.value?.npc) {
+ true -> Res.string.game_master__actions__hide_npc__description
+ else -> Res.string.game_master__actions__show_npc__description
+ },
+ onConfirmationRequest = {
+ try {
+ actionUseCase.toggleNpcVisibility()
+ } catch (exception: Exception) {
+ val message = ErrorSnackUio.from(exception = exception)
+ _error.emit(message)
+ }
+ },
+ )
+ }
+
+ private suspend inline fun showConfirmationDialog(
+ title: StringResource,
+ description: StringResource,
+ crossinline onConfirmationRequest: suspend () -> Unit,
+ crossinline onDismissRequest: () -> Unit = { _validationDialog.value = null },
+ ) {
+ _validationDialog.value = ConfirmationDialogUio(
+ title = getString(title),
+ description = getString(description),
+ onConfirmRequest = {
+ viewModelScope.launch {
+ onConfirmationRequest()
+ onDismissRequest()
+ }
+ },
+ onDismissRequest = {
+ onDismissRequest()
+ },
+ )
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterPage.kt
index 79899c8..b5e8c3a 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterPage.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterPage.kt
@@ -109,7 +109,6 @@ fun GMCharacterPage(
.width(width = 128.dp * 4)
.fillMaxHeight(),
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Rtl),
- blurController = blurController,
detailPanelViewModel = characterDetailViewModel,
characterDiminishedViewModel = dismissedViewModel,
characteristicDialogViewModel = characteristicDialogViewModel,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/settings/SettingsViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/settings/SettingsViewModel.kt
index 6560bb1..42fd420 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/settings/SettingsViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/settings/SettingsViewModel.kt
@@ -15,16 +15,8 @@ import lwacharactersheet.composeapp.generated.resources.ic_fan_focus_24dp
import lwacharactersheet.composeapp.generated.resources.ic_format_list_numbered_24dp
import lwacharactersheet.composeapp.generated.resources.ic_ifl_24dp
import lwacharactersheet.composeapp.generated.resources.ic_timer_24dp
-import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
-import lwacharactersheet.composeapp.generated.resources.ic_visibility_off_24dp
-import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_hide_delay_description
-import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_hide_delay_title
-import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_hide_description
-import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_hide_title
import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_scroll_description
import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_scroll_title
-import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_show_description
-import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_show_title
import lwacharactersheet.composeapp.generated.resources.settings__chat_log__line_count_description
import lwacharactersheet.composeapp.generated.resources.settings__chat_log__line_count_title
import lwacharactersheet.composeapp.generated.resources.settings__chat_log__section
@@ -92,48 +84,6 @@ class SettingsViewModel(
SettingSectionUio(
title = Res.string.settings__chat_log__section,
),
- SettingToggleItemUio(
- icon = Res.drawable.ic_visibility_24dp,
- title = Res.string.settings__chat_log__auto_show_title,
- description = Res.string.settings__chat_log__auto_show_description,
- checked = booleanStates.autoShowChat,
- onToggle = {
- settingsRepository.update(
- settings = settings.value.copy(
- chat = settings.value.chat.copy(autoShowChat = it)
- )
- )
- },
- ),
- SettingToggleItemUio(
- icon = Res.drawable.ic_visibility_off_24dp,
- title = Res.string.settings__chat_log__auto_hide_title,
- description = Res.string.settings__chat_log__auto_hide_description,
- checked = booleanStates.autoHideChat,
- onToggle = {
- settingsRepository.update(
- settings = settings.value.copy(
- chat = settings.value.chat.copy(autoHideChat = it)
- )
- )
- },
- ),
- SettingNumberItemUio(
- icon = Res.drawable.ic_timer_24dp,
- title = Res.string.settings__chat_log__auto_hide_delay_title,
- description = Res.string.settings__chat_log__auto_hide_delay_description,
- enable = booleanStates.autoHideChat,
- value = intStates.autoHideDelay,
- onValueChange = {
- if (it in 0..999) {
- settingsRepository.update(
- settings = settings.value.copy(
- chat = settings.value.chat.copy(autoHideDelay = it)
- )
- )
- }
- }
- ),
SettingToggleItemUio(
icon = Res.drawable.ic_fan_focus_24dp,
title = Res.string.settings__chat_log__auto_scroll_title,
@@ -170,9 +120,6 @@ class SettingsViewModel(
settingsRepository.settingsFlow().collect { settings ->
booleanStates.dynamicDice.value = settings.portrait.dynamicDice
intStates.dynamicDiceDelay.value = settings.portrait.dynamicDiceDelay
- booleanStates.autoShowChat.value = settings.chat.autoShowChat
- booleanStates.autoHideChat.value = settings.chat.autoHideChat
- intStates.autoHideDelay.value = settings.chat.autoHideDelay
booleanStates.autoScrollChat.value = settings.chat.autoScrollChat
intStates.maxLineCount.value = settings.chat.maxLineCount
}
@@ -197,15 +144,6 @@ class SettingsViewModel(
private val HashMap>.dynamicDiceDelay
get() = getOrPut("DYNAMIC_DICE_DELAY") { mutableStateOf(settings.value.portrait.dynamicDiceDelay) }
- private val HashMap>.autoShowChat
- get() = getOrPut("AUTO_SHOW_CHAT") { mutableStateOf(settings.value.chat.autoShowChat) }
-
- private val HashMap>.autoHideChat
- get() = getOrPut("AUTO_HIDE_CHAT") { mutableStateOf(settings.value.chat.autoHideChat) }
-
- private val HashMap>.autoHideDelay
- get() = getOrPut("AUTO_HIDE_DELAY") { mutableStateOf(settings.value.chat.autoHideDelay) }
-
private val HashMap>.autoScrollChat
get() = getOrPut("AUTO_SCROLL_CHAT") { mutableStateOf(settings.value.chat.autoScrollChat) }
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColors.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColors.kt
index 98bfedc..968d573 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColors.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColors.kt
@@ -55,7 +55,10 @@ data class LwaColors(
@Composable
@Stable
fun darkLwaColorTheme(
- base: Colors = darkColors(),
+ base: Colors = darkColors(
+ primary = Color(0xFFBB86FC),
+ primaryVariant = Color(0xB2BB86FC),
+ ),
elevated: LwaColors.Elevated = LwaColors.Elevated(
base1dp = base.calculateElevatedColor(
color = base.surface,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/usecase/SettingsUseCase.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/usecase/SettingsUseCase.kt
index 357b6ab..d4e325b 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/usecase/SettingsUseCase.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/usecase/SettingsUseCase.kt
@@ -8,12 +8,10 @@ class SettingsUseCase {
playerName = "",
portrait = Settings.Portrait(
dynamicDice = true,
- dynamicDiceDelay = 5000,
+ dynamicDiceDelay = 8000,
),
chat = Settings.Chat(
- autoHideChat = true,
- autoHideDelay = 8,
- autoShowChat = true,
+ showChat = true,
autoScrollChat = true,
maxLineCount = 200,
),
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 6d32363..fb24483 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -10,6 +10,7 @@ koin = "4.0.0"
turtle = "0.10.0"
logback = "1.5.17"
coil = "3.1.0"
+zoomable = "2.7.0"
ui-graphics-android = "1.7.8"
buildkonfig = "0.17.0"
@@ -35,6 +36,7 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-
# UI.
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
+engawapg-zoomable = { module = "net.engawapg.lib:zoomable", version.ref = "zoomable" }
# Injection with Koin
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" }