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_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/kotlin/com/pixelized/desktop/lwa/Module.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt
index 888e447..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,8 +40,8 @@ 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
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 de9e574..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
@@ -2,11 +2,11 @@ package com.pixelized.desktop.lwa.ui.screen.campaign
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
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
@@ -20,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
@@ -42,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
@@ -49,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
@@ -67,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(),
@@ -96,7 +100,9 @@ fun CampaignScreen(
},
main = {
-
+ Scene(
+ modifier = Modifier.matchParentSize(),
+ )
},
chat = {
CampaignChat(
@@ -115,9 +121,10 @@ fun CampaignScreen(
}
},
leftPanel = {
- PlayerRibbon(
+ CharacterRibbon(
modifier = Modifier.fillMaxHeight(),
viewModel = playerRibbonViewModel,
+ layoutDirection = LayoutDirection.Ltr,
onCharacterLeftClick = {
scope.launch {
playerDetailViewModel.showCharacter(
@@ -140,8 +147,10 @@ fun CampaignScreen(
)
},
rightPanel = {
- NpcRibbon(
+ CharacterRibbon(
modifier = Modifier.fillMaxHeight(),
+ viewModel = npcRibbonViewModel,
+ layoutDirection = LayoutDirection.Rtl,
onCharacterLeftClick = {
scope.launch {
npcDetailViewModel.showCharacter(
@@ -167,10 +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),
- detailPanelViewModel = npcDetailViewModel,
+ detailPanelViewModel = playerDetailViewModel,
characterDiminishedViewModel = dismissedViewModel,
characteristicDialogViewModel = characteristicDialogViewModel,
alterationViewModel = alterationViewModel,
@@ -183,10 +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),
- detailPanelViewModel = playerDetailViewModel,
+ detailPanelViewModel = npcDetailViewModel,
characterDiminishedViewModel = dismissedViewModel,
characteristicDialogViewModel = characteristicDialogViewModel,
alterationViewModel = alterationViewModel,
@@ -205,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()
@@ -307,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) }
@@ -314,6 +326,7 @@ private fun CampaignLayout(
val chatOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
val scope = remember {
CampaignLayoutScope(
+ main = mainState,
leftOverlay = leftOverlayState,
leftPanel = leftPanelState,
rightOverlay = rightOverlayState,
@@ -334,30 +347,35 @@ private fun CampaignLayout(
) {
Box(
modifier = Modifier
- .align(alignment = Alignment.Center)
- .fillMaxSize(),
+ .onSizeChanged { mainState.value = it.toDp(density) }
+ .matchParentSize(),
) {
main()
}
- Row {
- Box(
- modifier = Modifier.onSizeChanged { leftPanelState.value = it.toDp(density) },
- ) {
- leftPanel()
- }
- Box(
- modifier = Modifier
- .align(alignment = Alignment.Bottom)
- .weight(weight = 1f)
- .onSizeChanged { chatOverlayState.value = it.toDp(density) },
- ) {
- chat()
- }
- Box(
- modifier = Modifier.onSizeChanged { rightPanelState.value = it.toDp(density) },
- ) {
- rightPanel()
- }
+ Box(
+ modifier = Modifier
+ .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()
+ }
+ Box(
+ modifier = Modifier
+ .align(alignment = Alignment.CenterStart)
+ .onSizeChanged { leftPanelState.value = it.toDp(density) },
+ ) {
+ leftPanel()
+ }
+ Box(
+ modifier = Modifier
+ .align(alignment = Alignment.CenterEnd)
+ .onSizeChanged { rightPanelState.value = it.toDp(density) },
+ ) {
+ rightPanel()
}
Box(
modifier = Modifier
@@ -392,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
@@ -425,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/ribbon/CharacterRibbon.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/CharacterRibbon.kt
index 3e0b752..c67da46 100644
--- 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
@@ -17,14 +17,12 @@ 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 com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel
-import org.koin.compose.viewmodel.koinViewModel
@Composable
fun CharacterRibbon(
modifier: Modifier = Modifier,
layoutDirection: LayoutDirection,
- viewModel: PlayerRibbonViewModel = koinViewModel(),
+ viewModel: CharacterRibbonViewModel,
padding: PaddingValues = PaddingValues(all = 8.dp),
onCharacterLeftClick: (characterSheetId: String) -> Unit,
onCharacterRightClick: (characterSheetId: String) -> Unit,
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/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" }