diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_foggy_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_foggy_24dp.xml new file mode 100644 index 0000000..1c6e5d6 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_foggy_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_foggy_filled_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_foggy_filled_24dp.xml new file mode 100644 index 0000000..ba07634 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_foggy_filled_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/DahomeMap.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/DahomeMap.kt index 664ab2a..349665e 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/DahomeMap.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/DahomeMap.kt @@ -1,20 +1,22 @@ package com.pixelized.desktop.lwa.ui.composable.scene -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp import com.pixelized.desktop.lwa.ui.screen.campaign.LocalCampaignLayoutScope 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_foggy_24dp +import lwacharactersheet.composeapp.generated.resources.ic_foggy_filled_24dp import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp import lwacharactersheet.composeapp.generated.resources.ic_zoom_in_map_24dp import lwacharactersheet.composeapp.generated.resources.ic_zoom_out_map_24dp @@ -29,6 +31,7 @@ fun MapScene( ) { val campaign = LocalCampaignLayoutScope.current val scope = rememberCoroutineScope() + val fogOfWarEdit = remember { mutableStateOf(true) } val map = rememberLayoutFromResource( name = "Dahomé", @@ -68,7 +71,7 @@ fun MapScene( initialZoom = 1f, initialOffset = IntOffset(x = -150, y = -120), ), - fogOfWar = FogOfWar.NONE, + fogOfWar = FogOfWar(), layers = listOf( map, mapRegionOverlay, @@ -81,14 +84,21 @@ fun MapScene( ) } Scene( - modifier = modifier, + modifier = modifier.onFogOfWarControl( + scope = scope, + enable = fogOfWarEdit.value, + fogOfWar = scene.fogOfWar, + camera = scene.camera, + ), scene = scene, ) { - Column( + Row( modifier = Modifier - .align(alignment = Alignment.BottomEnd) - .padding(end = campaign.rightPanel.value.width) - .padding(all = 8.dp) + .align(alignment = Alignment.BottomStart) + .padding( + start = campaign.leftPanel.value.width, + bottom = campaign.chatOverlay.value.height, + ) ) { IconButton( onClick = { @@ -149,6 +159,19 @@ fun MapScene( contentDescription = null ) } + IconButton( + onClick = { + fogOfWarEdit.value = fogOfWarEdit.value.not() + } + ) { + Icon( + painter = when (fogOfWarEdit.value) { + true -> painterResource(Res.drawable.ic_foggy_filled_24dp) + else -> painterResource(Res.drawable.ic_foggy_24dp) + }, + contentDescription = null + ) + } } } } \ 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 index 7c77717..b8bec27 100644 --- 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 @@ -1,13 +1,78 @@ package com.pixelized.desktop.lwa.ui.composable.scene import androidx.compose.runtime.Stable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.input.pointer.PointerEvent +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.isPrimaryPressed +import androidx.compose.ui.input.pointer.onPointerEvent +import com.pixelized.desktop.lwa.ui.composable.scene.utils.global +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch @Stable -data class FogOfWar( - val color: Color = Color.Black.copy(alpha = 0.0f), +class FogOfWar( + val color: Color = Color.Black.copy(alpha = 0.5f), ) { + val path = Path() + + fun moveTo(position: Offset) { + path.moveTo(x = position.x, y = position.y) + } + + fun lineTo(position: Offset) { + path.lineTo(x = position.x, y = position.y) + } + companion object { val NONE = FogOfWar(color = Color.Transparent) } +} + +@OptIn(ExperimentalComposeUiApi::class) +fun Modifier.onFogOfWarControl( + scope: CoroutineScope, + enable: Boolean, + camera: Camera, + fogOfWar: FogOfWar, +) = if (!enable) { + this +} else { + var lastEventTime = System.currentTimeMillis() + var previousEvent: PointerEvent? = null + + this + .onPointerEvent(PointerEventType.Release) { event: PointerEvent -> + scope.launch { + println("PointerEventType.Release") + lastEventTime = System.currentTimeMillis() + previousEvent = null + } + } + .onPointerEvent(PointerEventType.Move) { event: PointerEvent -> + scope.launch { + val pointer = event.changes.firstOrNull() + val time = pointer?.uptimeMillis ?: 0L + + if (time - lastEventTime > 10L && event.buttons.isPrimaryPressed) { + if (previousEvent?.buttons?.isPrimaryPressed == true) { + println("PointerEventType.LineTo") + pointer?.position + ?.global(camera = camera) + ?.let(fogOfWar::lineTo) + } else { + println("PointerEventType.MoveTo") + pointer?.position + ?.global(camera = camera) + ?.let(fogOfWar::moveTo) + } + lastEventTime = System.currentTimeMillis() + previousEvent = event + } + } + } } \ 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 index 2a72545..21e6fb8 100644 --- 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 @@ -15,8 +15,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.scale +import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerEventType @@ -55,7 +61,6 @@ data class Scene( } - @Composable fun Scene( modifier: Modifier = Modifier, @@ -253,11 +258,40 @@ fun Modifier.drawElements( fun Modifier.drawFogOfWar( scene: Scene, -): Modifier = this.drawWithCache { - onDrawBehind { - drawRect(color = scene.fogOfWar.color) +): Modifier = this + .graphicsLayer( + compositingStrategy = CompositingStrategy.Offscreen + ) + .drawWithCache { + val stroke = Stroke( + width = 32f, + cap = StrokeCap.Round, + join = StrokeJoin.Round + ) + val fog = Color.Black.copy(alpha = 0.5f) + val color = Color.Transparent + onDrawBehind { + drawRect( + color = fog, + ) + scale( + scale = 1 / scene.camera.zoom, + pivot = Offset.Zero, + ) { + translate( + left = -scene.camera.offset.x.toFloat(), + top = -scene.camera.offset.y.toFloat(), + ) { + drawPath( + path = scene.fogOfWar.path, + style = stroke, + color = color, + blendMode = BlendMode.Clear, + ) + } + } + } } -} private data class CursorDelta( var lastDeltaTimestamp: Long = System.currentTimeMillis(), diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/SceneDebugPanel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/SceneDebugPanel.kt index 85a7df9..45c99b7 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/SceneDebugPanel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/SceneDebugPanel.kt @@ -1,14 +1,16 @@ package com.pixelized.desktop.lwa.ui.composable.scene +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Card import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf @@ -30,8 +32,10 @@ fun SceneDebugPanel( cursor: Cursor, scene: Scene, ) { - Card ( - modifier = modifier, + Card( + modifier = Modifier + .verticalScroll(state = rememberScrollState()) + .then(other = modifier), backgroundColor = MaterialTheme.lwa.colorScheme.elevated.base4dp, ) { Column( @@ -39,12 +43,15 @@ fun SceneDebugPanel( verticalArrangement = Arrangement.spacedBy(space = 8.dp), ) { SceneDebug( + modifier = Modifier.animateContentSize(), scene = scene, ) SceneCursorDebug( + modifier = Modifier.animateContentSize(), cursor = cursor, ) SceneCameraDebug( + modifier = Modifier.animateContentSize(), camera = scene.camera, ) Column { @@ -54,7 +61,9 @@ fun SceneDebugPanel( ) scene.layers.forEach { layer -> SceneLayerDebug( - modifier = Modifier.padding(start = MaterialTheme.lwa.dimen.debug.offset), + modifier = Modifier + .animateContentSize() + .padding(start = MaterialTheme.lwa.dimen.debug.offset), isOpen = false, layer = layer, ) @@ -67,7 +76,9 @@ fun SceneDebugPanel( ) scene.elements.forEach { element -> SceneElementDebug( - modifier = Modifier.padding(start = MaterialTheme.lwa.dimen.debug.offset), + modifier = Modifier + .animateContentSize() + .padding(start = MaterialTheme.lwa.dimen.debug.offset), isOpen = false, element = element, )