diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_frame_bug_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_frame_bug_24dp.xml
new file mode 100644
index 0000000..382f6bd
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_frame_bug_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_map_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_map_24dp.xml
new file mode 100644
index 0000000..b9b1446
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_map_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
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 7a9467f..3e25c6c 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt
@@ -41,8 +41,8 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.Characte
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory
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.chatbox.CampaignChatViewModel
+import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.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.campaign.toolbar.network.NetworkFactory
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
deleted file mode 100644
index 27d3733..0000000
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Camera.kt
+++ /dev/null
@@ -1,177 +0,0 @@
-package com.pixelized.desktop.lwa.ui.composable.scene
-
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.VectorConverter
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.widthIn
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.text.buildAnnotatedString
-import androidx.compose.ui.text.withStyle
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastRoundToInt
-import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneLayer
-import com.pixelized.desktop.lwa.ui.theme.lwa
-import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
-
-@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)
- }
- }
-}
-
-@Composable
-fun SceneCameraDebug(
- modifier: Modifier = Modifier,
- camera: Camera,
- isOpen: Boolean = true,
- style: LwaTypography.Debug = MaterialTheme.lwa.typography.debug,
- padding: Dp = MaterialTheme.lwa.dimen.debug.offset,
-) {
- val isOpen = remember { mutableStateOf(isOpen) }
-
- Column(
- modifier = Modifier
- .clickable { isOpen.value = isOpen.value.not() }
- .widthIn(min = MaterialTheme.lwa.dimen.debug.panelWidth)
- .then(other = modifier),
- verticalArrangement = Arrangement.spacedBy(space = 2.dp),
- ) {
- Text(
- style = MaterialTheme.lwa.typography.debug.title,
- color = MaterialTheme.lwa.colorScheme.base.primary,
- text = "Camera",
- )
- if (isOpen.value) {
- Text(
- modifier = Modifier.padding(start = padding),
- style = MaterialTheme.lwa.typography.debug.propertyValue,
- text = buildAnnotatedString {
- withStyle(style.propertyId) { append("offset: ") }
- append(camera.offset.toString())
- },
- )
- Text(
- modifier = Modifier.padding(start = padding),
- style = MaterialTheme.lwa.typography.debug.propertyValue,
- text = buildAnnotatedString {
- withStyle(style.propertyId) { append("size: ") }
- append(camera.cameraSize.toString())
- },
- )
- Text(
- modifier = Modifier.padding(start = padding),
- style = MaterialTheme.lwa.typography.debug.propertyValue,
- text = buildAnnotatedString {
- withStyle(style.propertyId) { append("zoom: ") }
- append(camera.zoom.toString())
- },
- )
- Text(
- modifier = Modifier.padding(start = padding),
- style = MaterialTheme.lwa.typography.debug.propertyValue,
- text = buildAnnotatedString {
- withStyle(style.propertyId) { append("size: ") }
- append(camera.cameraSizeZoomed.toString())
- },
- )
- }
- }
-}
\ No newline at end of file
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
deleted file mode 100644
index 349665e..0000000
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/DahomeMap.kt
+++ /dev/null
@@ -1,177 +0,0 @@
-package com.pixelized.desktop.lwa.ui.composable.scene
-
-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 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
-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.painterResource
-
-@Composable
-fun MapScene(
- modifier: Modifier = Modifier,
-) {
- val campaign = LocalCampaignLayoutScope.current
- val scope = rememberCoroutineScope()
- val fogOfWarEdit = remember { mutableStateOf(true) }
-
- val map = rememberLayoutFromResource(
- name = "Dahomé",
- resource = Res.drawable.image_dahome_maps,
- )
- val mapRegionOverlay = rememberLayoutFromResource(
- name = "Région",
- resource = Res.drawable.image_dahome_regions,
- )
- val element1 = rememberElementFromResource(
- name = "Group",
- resource = Res.drawable.icon_d100,
- offset = IntOffset(
- x = 2128,
- y = 1875,
- ),
- )
- val element2 = rememberElementFromResource(
- name = "End",
- resource = Res.drawable.icon_d100,
- offset = IntOffset(
- x = map.size.width,
- y = map.size.height,
- )
- )
- val element3 = rememberElementFromResource(
- name = "Start",
- resource = Res.drawable.icon_d100,
- offset = IntOffset(
- x = 0,
- y = 0,
- )
- )
- val scene = remember(map, mapRegionOverlay, element1, element2) {
- Scene(
- camera = Camera(
- initialZoom = 1f,
- initialOffset = IntOffset(x = -150, y = -120),
- ),
- fogOfWar = FogOfWar(),
- layers = listOf(
- map,
- mapRegionOverlay,
- ),
- elements = listOf(
- element1,
- element2,
- element3,
- ),
- )
- }
- Scene(
- modifier = modifier.onFogOfWarControl(
- scope = scope,
- enable = fogOfWarEdit.value,
- fogOfWar = scene.fogOfWar,
- camera = scene.camera,
- ),
- scene = scene,
- ) {
- Row(
- modifier = Modifier
- .align(alignment = Alignment.BottomStart)
- .padding(
- start = campaign.leftPanel.value.width,
- bottom = campaign.chatOverlay.value.height,
- )
- ) {
- 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.layers.getOrNull(1)?.let {
- it.alpha(alpha = if (it.alpha == 0f) 1f else 0f)
- }
- }
- }
- ) {
- Icon(
- painter = painterResource(Res.drawable.ic_visibility_24dp),
- 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/Scene.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Scene.kt
index 21e6fb8..dace034 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
@@ -1,56 +1,31 @@
package com.pixelized.desktop.lwa.ui.composable.scene
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.MaterialTheme
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.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
-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.toSize
+import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
+import com.pixelized.desktop.lwa.ui.composable.scene.camera.onCameraControl
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneElement
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneLayer
-import com.pixelized.desktop.lwa.ui.composable.scene.utils.global
import com.pixelized.desktop.lwa.ui.composable.scene.utils.local
-import com.pixelized.desktop.lwa.ui.theme.lwa
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.imageResource
import java.util.UUID
-import kotlin.math.sign
@Stable
data class Scene(
- val camera: Camera,
- val fogOfWar: FogOfWar,
val layers: List,
val elements: List,
) {
@@ -64,35 +39,21 @@ data class Scene(
@Composable
fun Scene(
modifier: Modifier = Modifier,
+ camera: Camera,
scene: Scene,
content: @Composable BoxScope.() -> Unit,
) {
val scope = rememberCoroutineScope()
- val cursors = remember {
- listOf(
- Cursor()
- )
- }
Box(
- modifier = modifier
+ modifier = Modifier
.graphicsLayer { clip = true }
- .onCameraControl(scope = scope, scene = scene)
- .onCursorControl(camera = scene.camera, cursor = cursors.first())
- .drawLayers(camera = scene.camera, layers = scene.layers)
- .drawElements(camera = scene.camera, elements = scene.elements)
- .drawCursors(camera = scene.camera, cursors = cursors)
- .drawFogOfWar(scene = scene),
+ .onCameraControl(scope = scope, sceneSize = scene.size, camera = camera)
+ .drawLayers(camera = camera, layers = scene.layers)
+ .drawElements(camera = camera, elements = scene.elements)
+ .then(other = modifier),
) {
content()
-
- SceneDebugPanel(
- modifier = Modifier
- .align(alignment = Alignment.TopEnd)
- .padding(paddingValues = MaterialTheme.lwa.dimen.paddingValues),
- cursor = cursors.first(),
- scene = scene,
- )
}
}
@@ -138,83 +99,6 @@ fun rememberElementFromResource(
}
}
-@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,
- )
- }
- }
-}
-
-@OptIn(ExperimentalComposeUiApi::class)
-fun Modifier.onCursorControl(
- camera: Camera,
- cursor: Cursor,
-): Modifier = this
- .onPointerEvent(PointerEventType.Exit) { event: PointerEvent ->
- cursor.change(
- position = Offset.Unspecified,
- )
- }
- .onPointerEvent(PointerEventType.Move) { event: PointerEvent ->
- cursor.change(
- position = event.changes.first().position.global(camera = camera),
- )
- }
-
-private fun Modifier.drawCursors(
- camera: Camera,
- cursors: List,
-): Modifier = this
- .drawWithCache {
- onDrawBehind {
- cursors.forEach { cursor ->
- drawRect(
- color = Color.Green,
- topLeft = cursor.offset.local(camera = camera),
- size = Size(10f, 10f),
- style = Stroke(width = 2f),
- )
- }
- }
- }
-
@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.drawLayers(
camera: Camera,
@@ -254,61 +138,4 @@ fun Modifier.drawElements(
)
}
}
-}
-
-fun Modifier.drawFogOfWar(
- scene: Scene,
-): 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(),
- 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/scene/camera/Camera.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/camera/Camera.kt
new file mode 100644
index 0000000..cd48fab
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/camera/Camera.kt
@@ -0,0 +1,97 @@
+package com.pixelized.desktop.lwa.ui.composable.scene.camera
+
+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(
+ power: Float,
+ snap: Boolean = false,
+ ) {
+ val value = _zoom.value * (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/camera/Modifier+onCameraControl.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/camera/Modifier+onCameraControl.kt
new file mode 100644
index 0000000..ddd3218
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/camera/Modifier+onCameraControl.kt
@@ -0,0 +1,75 @@
+package com.pixelized.desktop.lwa.ui.composable.scene.camera
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventType
+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.IntSize
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlin.math.sign
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
+fun Modifier.onCameraControl(
+ scope: CoroutineScope,
+ sceneSize: IntSize,
+ camera: Camera,
+): Modifier {
+ val offsetDelta = CursorDelta()
+ return this
+ .onSizeChanged {
+ camera.changeSizes(
+ sceneSize = sceneSize,
+ cameraSize = it,
+ )
+ }
+ .onPointerEvent(PointerEventType.Move) { event: PointerEvent ->
+ scope.launch {
+ offsetDelta.handlePositionChange(
+ event = event,
+ ) { delta ->
+ when {
+ event.buttons.isTertiaryPressed || (event.keyboardModifiers.isCtrlPressed && event.buttons.isPrimaryPressed) -> camera.handlePanning(
+ delta = delta,
+ snap = true,
+ )
+ }
+ }
+ }
+ }
+ .onPointerEvent(PointerEventType.Scroll) { event: PointerEvent ->
+ scope.launch {
+ camera.handleZoom(
+ power = -event.changes.first().scrollDelta.y.sign * 0.15f,
+ snap = false,
+ )
+ }
+ }
+}
+
+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/scene/cursor/Cursor.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/cursor/Cursor.kt
new file mode 100644
index 0000000..f9a9b78
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/cursor/Cursor.kt
@@ -0,0 +1,24 @@
+package com.pixelized.desktop.lwa.ui.composable.scene.cursor
+
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntOffset
+
+@Stable
+class Cursor(
+ initial: IntOffset = IntOffset.Zero,
+) {
+ private val _offset = mutableStateOf(initial)
+ val offset by _offset
+
+ fun change(
+ position: Offset,
+ ) {
+ _offset.value = IntOffset(
+ x = position.x.toInt(),
+ y = position.y.toInt(),
+ )
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/cursor/Modifier+drawCursor.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/cursor/Modifier+drawCursor.kt
new file mode 100644
index 0000000..d11ca1c
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/cursor/Modifier+drawCursor.kt
@@ -0,0 +1,26 @@
+package com.pixelized.desktop.lwa.ui.composable.scene.cursor
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.Stroke
+import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
+import com.pixelized.desktop.lwa.ui.composable.scene.utils.local
+
+fun Modifier.drawCursors(
+ camera: Camera,
+ cursors: List,
+): Modifier = this
+ .drawWithCache {
+ onDrawBehind {
+ cursors.forEach { cursor ->
+ drawRect(
+ color = Color.Green,
+ topLeft = cursor.offset.local(camera = camera),
+ size = Size(10f, 10f),
+ style = Stroke(width = 2f),
+ )
+ }
+ }
+ }
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/cursor/Modifier+onCursorControl.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/cursor/Modifier+onCursorControl.kt
new file mode 100644
index 0000000..c198f21
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/cursor/Modifier+onCursorControl.kt
@@ -0,0 +1,26 @@
+package com.pixelized.desktop.lwa.ui.composable.scene.cursor
+
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.onPointerEvent
+import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
+import com.pixelized.desktop.lwa.ui.composable.scene.utils.global
+
+@OptIn(ExperimentalComposeUiApi::class)
+fun Modifier.onCursorControl(
+ camera: Camera,
+ cursor: Cursor,
+): Modifier = this
+ .onPointerEvent(PointerEventType.Exit) { event: PointerEvent ->
+ cursor.change(
+ position = Offset.Unspecified,
+ )
+ }
+ .onPointerEvent(PointerEventType.Move) { event: PointerEvent ->
+ cursor.change(
+ position = event.changes.first().position.global(camera = camera),
+ )
+ }
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/debug/CameraDebugPanel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/debug/CameraDebugPanel.kt
new file mode 100644
index 0000000..596f1b4
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/debug/CameraDebugPanel.kt
@@ -0,0 +1,82 @@
+package com.pixelized.desktop.lwa.ui.composable.scene.debug
+
+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.padding
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
+import com.pixelized.desktop.lwa.ui.theme.lwa
+import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
+
+@Composable
+fun CameraDebugPanel(
+ modifier: Modifier = Modifier,
+ style: LwaTypography.Debug = MaterialTheme.lwa.typography.debug,
+ padding: Dp = MaterialTheme.lwa.dimen.debug.offset,
+ spacing: Dp = 2.dp,
+ camera: Camera,
+ isOpen: Boolean = true,
+) {
+ val isOpen = remember { mutableStateOf(isOpen) }
+
+ Column(
+ modifier = Modifier
+ .clickable { isOpen.value = isOpen.value.not() }
+ .widthIn(min = MaterialTheme.lwa.dimen.debug.panelWidth)
+ .animateContentSize()
+ .then(other = modifier),
+ verticalArrangement = Arrangement.spacedBy(space = spacing),
+ ) {
+ Text(
+ style = MaterialTheme.lwa.typography.debug.title,
+ color = MaterialTheme.lwa.colorScheme.base.primary,
+ text = "Camera",
+ )
+ if (isOpen.value) {
+ Text(
+ modifier = Modifier.padding(start = padding),
+ style = MaterialTheme.lwa.typography.debug.propertyValue,
+ text = buildAnnotatedString {
+ withStyle(style.propertyIdSpan) { append("offset: ") }
+ append(camera.offset.toString())
+ },
+ )
+ Text(
+ modifier = Modifier.padding(start = padding),
+ style = MaterialTheme.lwa.typography.debug.propertyValue,
+ text = buildAnnotatedString {
+ withStyle(style.propertyIdSpan) { append("size: ") }
+ append(camera.cameraSize.toString())
+ },
+ )
+ Text(
+ modifier = Modifier.padding(start = padding),
+ style = MaterialTheme.lwa.typography.debug.propertyValue,
+ text = buildAnnotatedString {
+ withStyle(style.propertyIdSpan) { append("zoom: ") }
+ append(camera.zoom.toString())
+ },
+ )
+ Text(
+ modifier = Modifier.padding(start = padding),
+ style = MaterialTheme.lwa.typography.debug.propertyValue,
+ text = buildAnnotatedString {
+ withStyle(style.propertyIdSpan) { append("projection: ") }
+ append(camera.cameraSizeZoomed.toString())
+ },
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Cursor.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/debug/CursorDebugPanel.kt
similarity index 59%
rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Cursor.kt
rename to composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/debug/CursorDebugPanel.kt
index a3ee09b..bdc5fb0 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Cursor.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/debug/CursorDebugPanel.kt
@@ -1,5 +1,6 @@
-package com.pixelized.desktop.lwa.ui.composable.scene
+package com.pixelized.desktop.lwa.ui.composable.scene.debug
+import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -8,44 +9,25 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
+import com.pixelized.desktop.lwa.ui.composable.scene.cursor.Cursor
import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
-@Stable
-class Cursor(
- initial: IntOffset = IntOffset.Zero,
-) {
- private val _offset = mutableStateOf(initial)
- val offset by _offset
-
- fun change(
- position: Offset,
- ) {
- _offset.value = IntOffset(
- x = position.x.toInt(),
- y = position.y.toInt(),
- )
- }
-}
-
@Composable
-fun SceneCursorDebug(
+fun CursorDebugPanel(
modifier: Modifier = Modifier,
- cursor: Cursor,
- isOpen: Boolean = true,
style: LwaTypography.Debug = MaterialTheme.lwa.typography.debug,
padding: Dp = MaterialTheme.lwa.dimen.debug.offset,
+ spacing: Dp = 2.dp,
+ cursors: List,
+ isOpen: Boolean = true,
) {
val isOpen = remember { mutableStateOf(isOpen) }
@@ -53,8 +35,9 @@ fun SceneCursorDebug(
modifier = Modifier
.clickable { isOpen.value = isOpen.value.not() }
.widthIn(min = MaterialTheme.lwa.dimen.debug.panelWidth)
+ .animateContentSize()
.then(other = modifier),
- verticalArrangement = Arrangement.spacedBy(space = 2.dp),
+ verticalArrangement = Arrangement.spacedBy(space = spacing),
) {
Text(
style = MaterialTheme.lwa.typography.debug.title,
@@ -62,14 +45,16 @@ fun SceneCursorDebug(
text = "Cursor",
)
if (isOpen.value) {
- Text(
- modifier = Modifier.padding(start = padding),
- style = MaterialTheme.lwa.typography.debug.propertyValue,
- text = buildAnnotatedString {
- withStyle(style.propertyId) { append("offset: ") }
- append(cursor.offset.toString())
- },
- )
+ cursors.forEach { cursor ->
+ Text(
+ modifier = Modifier.padding(start = padding),
+ style = MaterialTheme.lwa.typography.debug.propertyValue,
+ text = buildAnnotatedString {
+ withStyle(style.propertyIdSpan) { append("coordinate: ") }
+ append(cursor.offset.toString())
+ },
+ )
+ }
}
}
}
\ No newline at end of file
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/debug/SceneDebugPanel.kt
similarity index 58%
rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/SceneDebugPanel.kt
rename to composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/debug/SceneDebugPanel.kt
index 45c99b7..d2a0ec0 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/debug/SceneDebugPanel.kt
@@ -1,15 +1,11 @@
-package com.pixelized.desktop.lwa.ui.composable.scene
+package com.pixelized.desktop.lwa.ui.composable.scene.debug
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.Text
import androidx.compose.runtime.Composable
@@ -20,6 +16,7 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.pixelized.desktop.lwa.ui.composable.scene.Scene
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneElementDebug
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneLayerDebug
import com.pixelized.desktop.lwa.ui.theme.lwa
@@ -28,74 +25,11 @@ import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
@Composable
fun SceneDebugPanel(
modifier: Modifier = Modifier,
- paddingValues: PaddingValues = MaterialTheme.lwa.dimen.paddingValues,
- cursor: Cursor,
- scene: Scene,
-) {
- Card(
- modifier = Modifier
- .verticalScroll(state = rememberScrollState())
- .then(other = modifier),
- backgroundColor = MaterialTheme.lwa.colorScheme.elevated.base4dp,
- ) {
- Column(
- modifier = Modifier.padding(paddingValues = paddingValues),
- 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 {
- Text(
- style = MaterialTheme.lwa.typography.debug.title,
- text = "Layers:(${scene.layers.size})"
- )
- scene.layers.forEach { layer ->
- SceneLayerDebug(
- modifier = Modifier
- .animateContentSize()
- .padding(start = MaterialTheme.lwa.dimen.debug.offset),
- isOpen = false,
- layer = layer,
- )
- }
- }
- Column {
- Text(
- style = MaterialTheme.lwa.typography.debug.title,
- text = "Elements:(${scene.elements.size})"
- )
- scene.elements.forEach { element ->
- SceneElementDebug(
- modifier = Modifier
- .animateContentSize()
- .padding(start = MaterialTheme.lwa.dimen.debug.offset),
- isOpen = false,
- element = element,
- )
- }
- }
- }
- }
-
-}
-
-@Composable
-private fun SceneDebug(
- modifier: Modifier = Modifier,
- scene: Scene,
- isOpen: Boolean = true,
style: LwaTypography.Debug = MaterialTheme.lwa.typography.debug,
padding: Dp = MaterialTheme.lwa.dimen.debug.offset,
+ spacing: Dp = 2.dp,
+ scene: Scene,
+ isOpen: Boolean = true,
) {
val isOpen = remember { mutableStateOf(isOpen) }
@@ -103,8 +37,9 @@ private fun SceneDebug(
modifier = Modifier
.clickable { isOpen.value = isOpen.value.not() }
.widthIn(min = MaterialTheme.lwa.dimen.debug.panelWidth)
+ .animateContentSize()
.then(other = modifier),
- verticalArrangement = Arrangement.spacedBy(space = 2.dp),
+ verticalArrangement = Arrangement.spacedBy(space = spacing),
) {
Text(
style = MaterialTheme.lwa.typography.debug.title,
@@ -116,10 +51,40 @@ private fun SceneDebug(
modifier = Modifier.padding(start = padding),
style = MaterialTheme.lwa.typography.debug.propertyValue,
text = buildAnnotatedString {
- withStyle(style.propertyId) { append("Size: ") }
+ withStyle(style.propertyIdSpan) { append("Size: ") }
append(scene.size.toString())
},
)
+ Column(
+ modifier = Modifier.padding(start = padding),
+ ) {
+ Text(
+ style = MaterialTheme.lwa.typography.debug.propertyId,
+ text = "Layers:(${scene.layers.size})"
+ )
+ scene.layers.forEach { layer ->
+ SceneLayerDebug(
+ modifier = Modifier.padding(start = padding),
+ isOpen = false,
+ layer = layer,
+ )
+ }
+ }
+ Column(
+ modifier = Modifier.padding(start = padding),
+ ) {
+ Text(
+ style = MaterialTheme.lwa.typography.debug.propertyId,
+ text = "Elements:(${scene.elements.size})"
+ )
+ scene.elements.forEach { element ->
+ SceneElementDebug(
+ modifier = Modifier.padding(start = padding),
+ isOpen = false,
+ element = element,
+ )
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/drawables/SceneElement.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/drawables/SceneElement.kt
index fae7da7..a900c86 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/drawables/SceneElement.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/drawables/SceneElement.kt
@@ -71,7 +71,7 @@ fun SceneElementDebug(
modifier = Modifier.padding(start = padding),
style = MaterialTheme.lwa.typography.debug.propertyValue,
text = buildAnnotatedString {
- withStyle(style.propertyId) { append("id: ") }
+ withStyle(style.propertyIdSpan) { append("id: ") }
append(element.id)
},
)
@@ -79,7 +79,7 @@ fun SceneElementDebug(
modifier = Modifier.padding(start = padding),
style = MaterialTheme.lwa.typography.debug.propertyValue,
text = buildAnnotatedString {
- withStyle(style.propertyId) { append("offset: ") }
+ withStyle(style.propertyIdSpan) { append("offset: ") }
append(element.offset.toString())
},
)
@@ -87,7 +87,7 @@ fun SceneElementDebug(
modifier = Modifier.padding(start = padding),
style = MaterialTheme.lwa.typography.debug.propertyValue,
text = buildAnnotatedString {
- withStyle(style.propertyId) { append("size: ") }
+ withStyle(style.propertyIdSpan) { append("size: ") }
append(element.size.toString())
},
)
@@ -95,7 +95,7 @@ fun SceneElementDebug(
modifier = Modifier.padding(start = padding),
style = MaterialTheme.lwa.typography.debug.propertyValue,
text = buildAnnotatedString {
- withStyle(style.propertyId) { append("alpha: ") }
+ withStyle(style.propertyIdSpan) { append("alpha: ") }
append(element.alpha.toString())
},
)
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/drawables/SceneLayer.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/drawables/SceneLayer.kt
index 6705afe..4ac34e1 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/drawables/SceneLayer.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/drawables/SceneLayer.kt
@@ -57,7 +57,7 @@ fun SceneLayerDebug(
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
) {
Text(
- style = MaterialTheme.lwa.typography.debug.title,
+ style = MaterialTheme.lwa.typography.debug.propertyId,
color = MaterialTheme.lwa.colorScheme.base.primary,
text = layer.name,
)
@@ -66,7 +66,7 @@ fun SceneLayerDebug(
modifier = Modifier.padding(start = padding),
style = MaterialTheme.lwa.typography.debug.propertyValue,
text = buildAnnotatedString {
- withStyle(style.propertyId) { append("id: ") }
+ withStyle(style.propertyIdSpan) { append("id: ") }
append(layer.id)
},
)
@@ -74,7 +74,7 @@ fun SceneLayerDebug(
modifier = Modifier.padding(start = padding),
style = MaterialTheme.lwa.typography.debug.propertyValue,
text = buildAnnotatedString {
- withStyle(style.propertyId) { append("offset: ") }
+ withStyle(style.propertyIdSpan) { append("offset: ") }
append(layer.offset.toString())
},
)
@@ -82,7 +82,7 @@ fun SceneLayerDebug(
modifier = Modifier.padding(start = padding),
style = MaterialTheme.lwa.typography.debug.propertyValue,
text = buildAnnotatedString {
- withStyle(style.propertyId) { append("size: ") }
+ withStyle(style.propertyIdSpan) { append("size: ") }
append(layer.size.toString())
},
)
@@ -90,7 +90,7 @@ fun SceneLayerDebug(
modifier = Modifier.padding(start = padding),
style = MaterialTheme.lwa.typography.debug.propertyValue,
text = buildAnnotatedString {
- withStyle(style.propertyId) { append("alpha: ") }
+ withStyle(style.propertyIdSpan) { append("alpha: ") }
append(layer.alpha.toString())
},
)
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/fogOfWar/FogOfWar.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/fogOfWar/FogOfWar.kt
new file mode 100644
index 0000000..e68ebdb
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/fogOfWar/FogOfWar.kt
@@ -0,0 +1,44 @@
+package com.pixelized.desktop.lwa.ui.composable.scene.fogOfWar
+
+import androidx.compose.runtime.Stable
+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.BlendMode
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.CompositingStrategy
+import androidx.compose.ui.graphics.Path
+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
+import androidx.compose.ui.input.pointer.isPrimaryPressed
+import androidx.compose.ui.input.pointer.onPointerEvent
+import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
+import com.pixelized.desktop.lwa.ui.composable.scene.utils.global
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+@Stable
+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)
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/fogOfWar/Modifier+drawFogOfWar.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/fogOfWar/Modifier+drawFogOfWar.kt
new file mode 100644
index 0000000..0e8e7ec
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/fogOfWar/Modifier+drawFogOfWar.kt
@@ -0,0 +1,53 @@
+package com.pixelized.desktop.lwa.ui.composable.scene.fogOfWar
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.geometry.Offset
+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 com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
+
+fun Modifier.drawFogOfWar(
+ camera: Camera,
+ fogOfWar: FogOfWar,
+): 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 / camera.zoom,
+ pivot = Offset.Zero,
+ ) {
+ translate(
+ left = -camera.offset.x.toFloat(),
+ top = -camera.offset.y.toFloat(),
+ ) {
+ drawPath(
+ path = fogOfWar.path,
+ style = stroke,
+ color = color,
+ blendMode = BlendMode.Clear,
+ )
+ }
+ }
+ }
+ }
\ 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/Modifier+onFogOfWarControl.kt
similarity index 74%
rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/FogOfWar.kt
rename to composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/fogOfWar/Modifier+onFogOfWarControl.kt
index b8bec27..3d8f576 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/Modifier+onFogOfWarControl.kt
@@ -1,38 +1,16 @@
-package com.pixelized.desktop.lwa.ui.composable.scene
+package com.pixelized.desktop.lwa.ui.composable.scene.fogOfWar
-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.camera.Camera
import com.pixelized.desktop.lwa.ui.composable.scene.utils.global
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
-@Stable
-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,
@@ -46,7 +24,7 @@ fun Modifier.onFogOfWarControl(
var previousEvent: PointerEvent? = null
this
- .onPointerEvent(PointerEventType.Release) { event: PointerEvent ->
+ .onPointerEvent(PointerEventType.Release) { _: PointerEvent ->
scope.launch {
println("PointerEventType.Release")
lastEventTime = System.currentTimeMillis()
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/utils/Offset+Camera.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/utils/Offset+Coordinate.kt
similarity index 94%
rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/utils/Offset+Camera.kt
rename to composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/utils/Offset+Coordinate.kt
index 853a544..603a99f 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/utils/Offset+Camera.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/utils/Offset+Coordinate.kt
@@ -2,7 +2,7 @@ package com.pixelized.desktop.lwa.ui.composable.scene.utils
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntOffset
-import com.pixelized.desktop.lwa.ui.composable.scene.Camera
+import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
/**
* Convert local positon to global one.
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/window/WindowNavHost.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/window/WindowNavHost.kt
index 206ac66..e86c7f4 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/window/WindowNavHost.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/window/WindowNavHost.kt
@@ -34,7 +34,7 @@ class WindowController(
private val _windows = mutableStateOf