Add a debug panel to the scene + MapScene + Cursor
This commit is contained in:
		
							parent
							
								
									4e013aa358
								
							
						
					
					
						commit
						ae2c3da582
					
				
					 13 changed files with 858 additions and 153 deletions
				
			
		| 
						 | 
				
			
			@ -2,15 +2,32 @@ 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(
 | 
			
		||||
| 
						 | 
				
			
			@ -98,4 +115,63 @@ class Camera(
 | 
			
		|||
            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())
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,75 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.ui.composable.scene
 | 
			
		||||
 | 
			
		||||
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.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.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(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    cursor: Cursor,
 | 
			
		||||
    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 = "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())
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,154 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.ui.composable.scene
 | 
			
		||||
 | 
			
		||||
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.runtime.Composable
 | 
			
		||||
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_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 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.NONE,
 | 
			
		||||
            layers = listOf(
 | 
			
		||||
                map,
 | 
			
		||||
                mapRegionOverlay,
 | 
			
		||||
            ),
 | 
			
		||||
            elements = listOf(
 | 
			
		||||
                element1,
 | 
			
		||||
                element2,
 | 
			
		||||
                element3,
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
    Scene(
 | 
			
		||||
        modifier = modifier,
 | 
			
		||||
        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.layers.getOrNull(1)?.let {
 | 
			
		||||
                            it.alpha(alpha = if (it.alpha == 0f) 1f else 0f)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ) {
 | 
			
		||||
                Icon(
 | 
			
		||||
                    painter = painterResource(Res.drawable.ic_visibility_24dp),
 | 
			
		||||
                    contentDescription = null
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,5 +5,9 @@ import androidx.compose.ui.graphics.Color
 | 
			
		|||
 | 
			
		||||
@Stable
 | 
			
		||||
data class FogOfWar(
 | 
			
		||||
    val color: Color = Color.Black.copy(alpha = 0.5f),
 | 
			
		||||
)
 | 
			
		||||
    val color: Color = Color.Black.copy(alpha = 0.0f),
 | 
			
		||||
) {
 | 
			
		||||
    companion object {
 | 
			
		||||
        val NONE = FogOfWar(color = Color.Transparent)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,12 +2,9 @@ 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.BoxScope
 | 
			
		||||
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.material.MaterialTheme
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.Stable
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
| 
						 | 
				
			
			@ -17,6 +14,9 @@ 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.Color
 | 
			
		||||
import androidx.compose.ui.graphics.drawscope.Stroke
 | 
			
		||||
import androidx.compose.ui.graphics.graphicsLayer
 | 
			
		||||
import androidx.compose.ui.input.pointer.PointerEvent
 | 
			
		||||
import androidx.compose.ui.input.pointer.PointerEventType
 | 
			
		||||
| 
						 | 
				
			
			@ -28,163 +28,107 @@ 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 androidx.compose.ui.unit.toSize
 | 
			
		||||
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 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 java.util.UUID
 | 
			
		||||
import kotlin.math.sign
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
data class Scene(
 | 
			
		||||
    val camera: Camera,
 | 
			
		||||
    val layouts: List<Layout>,
 | 
			
		||||
    val fogOfWar: FogOfWar,
 | 
			
		||||
    val layers: List<SceneLayer>,
 | 
			
		||||
    val elements: List<SceneElement>,
 | 
			
		||||
) {
 | 
			
		||||
    val size: IntSize = IntSize(
 | 
			
		||||
        width = layouts.maxOf { it.size.width },
 | 
			
		||||
        height = layouts.maxOf { it.size.height },
 | 
			
		||||
        width = layers.maxOf { it.size.width },
 | 
			
		||||
        height = layers.maxOf { it.size.height },
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun Scene(
 | 
			
		||||
    modifier: Modifier,
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    scene: Scene,
 | 
			
		||||
    content: @Composable BoxScope.() -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    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),
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    val cursors = remember {
 | 
			
		||||
        listOf(
 | 
			
		||||
            Cursor()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = modifier
 | 
			
		||||
            .graphicsLayer { clip = true }
 | 
			
		||||
            .onCameraControl(scope = scope, scene = scene)
 | 
			
		||||
            .drawScene(scene = scene)
 | 
			
		||||
            .fogOfWar(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),
 | 
			
		||||
    ) {
 | 
			
		||||
        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
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
        content()
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
@Stable
 | 
			
		||||
fun rememberLayoutFromResource(
 | 
			
		||||
    resource: DrawableResource,
 | 
			
		||||
    offset: IntOffset = IntOffset.Zero,
 | 
			
		||||
): Layout {
 | 
			
		||||
    val texture = imageResource(
 | 
			
		||||
        resource = resource,
 | 
			
		||||
    )
 | 
			
		||||
    return remember(resource) {
 | 
			
		||||
        Layout(
 | 
			
		||||
            texture = texture,
 | 
			
		||||
            offset = offset,
 | 
			
		||||
        SceneDebugPanel(
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .align(alignment = Alignment.TopEnd)
 | 
			
		||||
                .padding(paddingValues = MaterialTheme.lwa.dimen.paddingValues),
 | 
			
		||||
            cursor = cursors.first(),
 | 
			
		||||
            scene = scene,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
@Stable
 | 
			
		||||
fun rememberScene(
 | 
			
		||||
    camera: Camera,
 | 
			
		||||
    fogOfWar: FogOfWar,
 | 
			
		||||
    vararg layouts: Layout,
 | 
			
		||||
): Scene {
 | 
			
		||||
    return remember {
 | 
			
		||||
        Scene(
 | 
			
		||||
            camera = camera,
 | 
			
		||||
            layouts = layouts.toList(),
 | 
			
		||||
            fogOfWar = fogOfWar,
 | 
			
		||||
fun rememberLayoutFromResource(
 | 
			
		||||
    name: String,
 | 
			
		||||
    resource: DrawableResource,
 | 
			
		||||
    offset: IntOffset = IntOffset.Zero,
 | 
			
		||||
): SceneLayer {
 | 
			
		||||
    val texture = imageResource(
 | 
			
		||||
        resource = resource,
 | 
			
		||||
    )
 | 
			
		||||
    return remember(resource) {
 | 
			
		||||
        SceneLayer(
 | 
			
		||||
            id = UUID.randomUUID().toString(),
 | 
			
		||||
            name = name,
 | 
			
		||||
            texture = texture,
 | 
			
		||||
            offset = offset,
 | 
			
		||||
            alpha = 1f,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
@Stable
 | 
			
		||||
fun rememberElementFromResource(
 | 
			
		||||
    name: String,
 | 
			
		||||
    resource: DrawableResource,
 | 
			
		||||
    offset: IntOffset = IntOffset.Zero,
 | 
			
		||||
): SceneElement {
 | 
			
		||||
    val texture = imageResource(
 | 
			
		||||
        resource = resource,
 | 
			
		||||
    )
 | 
			
		||||
    return remember(resource) {
 | 
			
		||||
        SceneElement(
 | 
			
		||||
            id = UUID.randomUUID().toString(),
 | 
			
		||||
            name = name,
 | 
			
		||||
            texture = texture,
 | 
			
		||||
            offset = offset,
 | 
			
		||||
            alpha = 1f,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -233,23 +177,81 @@ fun Modifier.onCameraControl(
 | 
			
		|||
        }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Modifier.drawScene(
 | 
			
		||||
    scene: Scene,
 | 
			
		||||
@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<Cursor>,
 | 
			
		||||
): 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,
 | 
			
		||||
    layers: List<SceneLayer>,
 | 
			
		||||
): Modifier = this.drawWithCache {
 | 
			
		||||
    onDrawBehind {
 | 
			
		||||
        scene.layouts.forEach { layout ->
 | 
			
		||||
        layers.forEach { layers ->
 | 
			
		||||
            drawImage(
 | 
			
		||||
                image = layout.texture,
 | 
			
		||||
                srcOffset = scene.camera.offset - layout.offset,
 | 
			
		||||
                srcSize = scene.camera.cameraSizeZoomed,
 | 
			
		||||
                dstSize = scene.camera.cameraSize,
 | 
			
		||||
                alpha = layout.alpha,
 | 
			
		||||
                image = layers.texture,
 | 
			
		||||
                srcOffset = camera.offset - layers.offset,
 | 
			
		||||
                srcSize = camera.cameraSizeZoomed,
 | 
			
		||||
                dstSize = camera.cameraSize,
 | 
			
		||||
                alpha = layers.alpha,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Modifier.fogOfWar(
 | 
			
		||||
fun Modifier.drawElements(
 | 
			
		||||
    camera: Camera,
 | 
			
		||||
    elements: List<SceneElement>,
 | 
			
		||||
): Modifier = this.drawWithCache {
 | 
			
		||||
    onDrawBehind {
 | 
			
		||||
        elements.forEach { element ->
 | 
			
		||||
            drawImage(
 | 
			
		||||
                image = element.texture,
 | 
			
		||||
                srcOffset = camera.offset - element.position,
 | 
			
		||||
                srcSize = camera.cameraSizeZoomed,
 | 
			
		||||
                dstSize = camera.cameraSize,
 | 
			
		||||
                alpha = element.alpha,
 | 
			
		||||
            )
 | 
			
		||||
            drawRect(
 | 
			
		||||
                color = Color.Red,
 | 
			
		||||
                topLeft = element.position.local(camera = camera),
 | 
			
		||||
                size = (element.size).toSize() / camera.zoom,
 | 
			
		||||
                style = Stroke(width = 2f),
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Modifier.drawFogOfWar(
 | 
			
		||||
    scene: Scene,
 | 
			
		||||
): Modifier = this.drawWithCache {
 | 
			
		||||
    onDrawBehind {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,114 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.ui.composable.scene
 | 
			
		||||
 | 
			
		||||
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.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
 | 
			
		||||
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.drawables.SceneElementDebug
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneLayerDebug
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.theme.lwa
 | 
			
		||||
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,
 | 
			
		||||
        backgroundColor = MaterialTheme.lwa.colorScheme.elevated.base4dp,
 | 
			
		||||
    ) {
 | 
			
		||||
        Column(
 | 
			
		||||
            modifier = Modifier.padding(paddingValues = paddingValues),
 | 
			
		||||
            verticalArrangement = Arrangement.spacedBy(space = 8.dp),
 | 
			
		||||
        ) {
 | 
			
		||||
            SceneDebug(
 | 
			
		||||
                scene = scene,
 | 
			
		||||
            )
 | 
			
		||||
            SceneCursorDebug(
 | 
			
		||||
                cursor = cursor,
 | 
			
		||||
            )
 | 
			
		||||
            SceneCameraDebug(
 | 
			
		||||
                camera = scene.camera,
 | 
			
		||||
            )
 | 
			
		||||
            Column {
 | 
			
		||||
                Text(
 | 
			
		||||
                    style = MaterialTheme.lwa.typography.debug.title,
 | 
			
		||||
                    text = "Layers:(${scene.layers.size})"
 | 
			
		||||
                )
 | 
			
		||||
                scene.layers.forEach { layer ->
 | 
			
		||||
                    SceneLayerDebug(
 | 
			
		||||
                        modifier = Modifier.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.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,
 | 
			
		||||
) {
 | 
			
		||||
    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 = "Scene",
 | 
			
		||||
        )
 | 
			
		||||
        if (isOpen.value) {
 | 
			
		||||
            Text(
 | 
			
		||||
                modifier = Modifier.padding(start = padding),
 | 
			
		||||
                style = MaterialTheme.lwa.typography.debug.propertyValue,
 | 
			
		||||
                text = buildAnnotatedString {
 | 
			
		||||
                    withStyle(style.propertyId) { append("Size: ") }
 | 
			
		||||
                    append(scene.size.toString())
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,22 +1,22 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.ui.composable.scene
 | 
			
		||||
package com.pixelized.desktop.lwa.ui.composable.scene.drawables
 | 
			
		||||
 | 
			
		||||
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(
 | 
			
		||||
open class SceneDrawable(
 | 
			
		||||
    val id: String,
 | 
			
		||||
    val name: String,
 | 
			
		||||
    val texture: ImageBitmap,
 | 
			
		||||
    val offset: IntOffset = IntOffset.Zero,
 | 
			
		||||
    val size: IntSize = IntSize(texture.width, texture.height),
 | 
			
		||||
    private val initialAlpha: Float = 1f,
 | 
			
		||||
    val offset: IntOffset,
 | 
			
		||||
    val size: IntSize,
 | 
			
		||||
    private val initialAlpha: Float,
 | 
			
		||||
) {
 | 
			
		||||
    private val _alpha = Animatable(
 | 
			
		||||
        initialValue = initialAlpha,
 | 
			
		||||
        typeConverter = Float.VectorConverter,
 | 
			
		||||
        typeConverter = Float.Companion.VectorConverter,
 | 
			
		||||
    )
 | 
			
		||||
    val alpha get() = _alpha.value
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,104 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.ui.composable.scene.drawables
 | 
			
		||||
 | 
			
		||||
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.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.ImageBitmap
 | 
			
		||||
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 com.pixelized.desktop.lwa.ui.theme.lwa
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
class SceneElement(
 | 
			
		||||
    id: String,
 | 
			
		||||
    name: String,
 | 
			
		||||
    texture: ImageBitmap,
 | 
			
		||||
    offset: IntOffset = IntOffset.Companion.Zero,
 | 
			
		||||
    size: IntSize = IntSize(texture.width, texture.height),
 | 
			
		||||
    alpha: Float = 1f,
 | 
			
		||||
) : SceneDrawable(
 | 
			
		||||
    id = id,
 | 
			
		||||
    name = name,
 | 
			
		||||
    texture = texture,
 | 
			
		||||
    offset = offset,
 | 
			
		||||
    size = size,
 | 
			
		||||
    initialAlpha = alpha,
 | 
			
		||||
) {
 | 
			
		||||
    val position = IntOffset(
 | 
			
		||||
        x = offset.x - size.width / 2,
 | 
			
		||||
        y = offset.y - size.height / 2,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun SceneElementDebug(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    element: SceneElement,
 | 
			
		||||
    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 = element.name,
 | 
			
		||||
        )
 | 
			
		||||
        if (isOpen.value) {
 | 
			
		||||
            Text(
 | 
			
		||||
                modifier = Modifier.padding(start = padding),
 | 
			
		||||
                style = MaterialTheme.lwa.typography.debug.propertyValue,
 | 
			
		||||
                text = buildAnnotatedString {
 | 
			
		||||
                    withStyle(style.propertyId) { append("id: ") }
 | 
			
		||||
                    append(element.id)
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
            Text(
 | 
			
		||||
                modifier = Modifier.padding(start = padding),
 | 
			
		||||
                style = MaterialTheme.lwa.typography.debug.propertyValue,
 | 
			
		||||
                text = buildAnnotatedString {
 | 
			
		||||
                    withStyle(style.propertyId) { append("offset: ") }
 | 
			
		||||
                    append(element.offset.toString())
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
            Text(
 | 
			
		||||
                modifier = Modifier.padding(start = padding),
 | 
			
		||||
                style = MaterialTheme.lwa.typography.debug.propertyValue,
 | 
			
		||||
                text = buildAnnotatedString {
 | 
			
		||||
                    withStyle(style.propertyId) { append("size: ") }
 | 
			
		||||
                    append(element.size.toString())
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
            Text(
 | 
			
		||||
                modifier = Modifier.padding(start = padding),
 | 
			
		||||
                style = MaterialTheme.lwa.typography.debug.propertyValue,
 | 
			
		||||
                text = buildAnnotatedString {
 | 
			
		||||
                    withStyle(style.propertyId) { append("alpha: ") }
 | 
			
		||||
                    append(element.alpha.toString())
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,99 @@
 | 
			
		|||
package com.pixelized.desktop.lwa.ui.composable.scene.drawables
 | 
			
		||||
 | 
			
		||||
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.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.ImageBitmap
 | 
			
		||||
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 com.pixelized.desktop.lwa.ui.theme.lwa
 | 
			
		||||
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
class SceneLayer(
 | 
			
		||||
    id : String,
 | 
			
		||||
    name: String,
 | 
			
		||||
    texture: ImageBitmap,
 | 
			
		||||
    offset: IntOffset = IntOffset.Companion.Zero,
 | 
			
		||||
    size: IntSize = IntSize(texture.width, texture.height),
 | 
			
		||||
    alpha: Float = 1f,
 | 
			
		||||
) : SceneDrawable(
 | 
			
		||||
    id = id,
 | 
			
		||||
    name = name,
 | 
			
		||||
    texture = texture,
 | 
			
		||||
    offset = offset,
 | 
			
		||||
    size = size,
 | 
			
		||||
    initialAlpha = alpha,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun SceneLayerDebug(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    layer: SceneLayer,
 | 
			
		||||
    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 = layer.name,
 | 
			
		||||
        )
 | 
			
		||||
        if (isOpen.value) {
 | 
			
		||||
            Text(
 | 
			
		||||
                modifier = Modifier.padding(start = padding),
 | 
			
		||||
                style = MaterialTheme.lwa.typography.debug.propertyValue,
 | 
			
		||||
                text = buildAnnotatedString {
 | 
			
		||||
                    withStyle(style.propertyId) { append("id: ") }
 | 
			
		||||
                    append(layer.id)
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
            Text(
 | 
			
		||||
                modifier = Modifier.padding(start = padding),
 | 
			
		||||
                style = MaterialTheme.lwa.typography.debug.propertyValue,
 | 
			
		||||
                text = buildAnnotatedString {
 | 
			
		||||
                    withStyle(style.propertyId) { append("offset: ") }
 | 
			
		||||
                    append(layer.offset.toString())
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
            Text(
 | 
			
		||||
                modifier = Modifier.padding(start = padding),
 | 
			
		||||
                style = MaterialTheme.lwa.typography.debug.propertyValue,
 | 
			
		||||
                text = buildAnnotatedString {
 | 
			
		||||
                    withStyle(style.propertyId) { append("size: ") }
 | 
			
		||||
                    append(layer.size.toString())
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
            Text(
 | 
			
		||||
                modifier = Modifier.padding(start = padding),
 | 
			
		||||
                style = MaterialTheme.lwa.typography.debug.propertyValue,
 | 
			
		||||
                text = buildAnnotatedString {
 | 
			
		||||
                    withStyle(style.propertyId) { append("alpha: ") }
 | 
			
		||||
                    append(layer.alpha.toString())
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
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
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert local positon to global one.
 | 
			
		||||
 * Global position are agnostic from camera, and therefor should be use to position stuff on the map.
 | 
			
		||||
 * A common use case is to share players cursor.
 | 
			
		||||
 */
 | 
			
		||||
fun Offset.global(
 | 
			
		||||
    camera: Camera,
 | 
			
		||||
): Offset = Offset(
 | 
			
		||||
    x = this.x * camera.zoom + camera.offset.x,
 | 
			
		||||
    y = this.y * camera.zoom + camera.offset.y,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert global positon to local one.
 | 
			
		||||
 * Local position take into account the camera and are use to display stuff on the Scene composable.
 | 
			
		||||
 */
 | 
			
		||||
fun Offset.local(
 | 
			
		||||
    camera: Camera,
 | 
			
		||||
): Offset = Offset(
 | 
			
		||||
    x = (this.x - camera.offset.x) / camera.zoom,
 | 
			
		||||
    y = (this.y - camera.offset.y) / camera.zoom,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert global positon to local one.
 | 
			
		||||
 * Local position take into account the camera and are use to display stuff on the Scene composable.
 | 
			
		||||
 */
 | 
			
		||||
fun IntOffset.local(
 | 
			
		||||
    camera: Camera,
 | 
			
		||||
): Offset = Offset(
 | 
			
		||||
    x = (this.x.toFloat() - camera.offset.x) / camera.zoom,
 | 
			
		||||
    y = (this.y.toFloat() - camera.offset.y) / camera.zoom,
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +44,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.composable.scene.MapScene
 | 
			
		||||
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
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +101,7 @@ fun CampaignScreen(
 | 
			
		|||
 | 
			
		||||
                },
 | 
			
		||||
                main = {
 | 
			
		||||
                    Scene(
 | 
			
		||||
                    MapScene(
 | 
			
		||||
                        modifier = Modifier.matchParentSize(),
 | 
			
		||||
                    )
 | 
			
		||||
                },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@ data class LwaDimen(
 | 
			
		|||
    val layout: Layout,
 | 
			
		||||
    val portrait: Portrait,
 | 
			
		||||
    val sheet: Sheet,
 | 
			
		||||
    val debug: Debug,
 | 
			
		||||
) {
 | 
			
		||||
    @Stable
 | 
			
		||||
    data class Layout(
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +34,12 @@ data class LwaDimen(
 | 
			
		|||
        val subCategory: Dp,
 | 
			
		||||
        val characteristic: DpSize,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @Stable
 | 
			
		||||
    data class Debug(
 | 
			
		||||
        val panelWidth: Dp,
 | 
			
		||||
        val offset: Dp,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
| 
						 | 
				
			
			@ -52,6 +59,10 @@ fun lwaDimen(
 | 
			
		|||
        detailWidth = 128.dp * 4,
 | 
			
		||||
        chatMaxWidth = 600.dp,
 | 
			
		||||
    ),
 | 
			
		||||
    debug: LwaDimen.Debug = LwaDimen.Debug(
 | 
			
		||||
        panelWidth = 200.dp,
 | 
			
		||||
        offset = 8.dp,
 | 
			
		||||
    )
 | 
			
		||||
): LwaDimen {
 | 
			
		||||
    return remember {
 | 
			
		||||
        LwaDimen(
 | 
			
		||||
| 
						 | 
				
			
			@ -60,6 +71,7 @@ fun lwaDimen(
 | 
			
		|||
            portrait = portrait,
 | 
			
		||||
            sheet = sheet,
 | 
			
		||||
            layout = layout,
 | 
			
		||||
            debug = debug,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.Shadow
 | 
			
		|||
import androidx.compose.ui.graphics.StrokeJoin
 | 
			
		||||
import androidx.compose.ui.graphics.drawscope.Fill
 | 
			
		||||
import androidx.compose.ui.graphics.drawscope.Stroke
 | 
			
		||||
import androidx.compose.ui.text.SpanStyle
 | 
			
		||||
import androidx.compose.ui.text.TextStyle
 | 
			
		||||
import androidx.compose.ui.text.font.FontFamily
 | 
			
		||||
import androidx.compose.ui.text.font.FontStyle
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +31,7 @@ data class LwaTypography(
 | 
			
		|||
    val characterSheet: CharacterSheet,
 | 
			
		||||
    val inventory: Inventory,
 | 
			
		||||
    val freeDiceThrow: Dice,
 | 
			
		||||
    val debug: Debug,
 | 
			
		||||
) {
 | 
			
		||||
    @Stable
 | 
			
		||||
    data class Chat(
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +75,13 @@ data class LwaTypography(
 | 
			
		|||
        val dice: TextStyle,
 | 
			
		||||
        val result: TextStyle,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @Stable
 | 
			
		||||
    data class Debug(
 | 
			
		||||
        val title: TextStyle,
 | 
			
		||||
        val propertyId: SpanStyle,
 | 
			
		||||
        val propertyValue: TextStyle,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
| 
						 | 
				
			
			@ -196,7 +205,24 @@ fun lwaTypography(
 | 
			
		|||
                result = robotoMono.h4.copy(
 | 
			
		||||
                    color = colors.base.onSurface,
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
            ),
 | 
			
		||||
            debug = LwaTypography.Debug(
 | 
			
		||||
                title = robotoMono.caption.copy(
 | 
			
		||||
                    fontWeight = FontWeight.Medium,
 | 
			
		||||
                    fontSize = 14.sp,
 | 
			
		||||
                    lineHeight = 14.sp,
 | 
			
		||||
                ),
 | 
			
		||||
                propertyId = robotoMono.caption.copy(
 | 
			
		||||
                    fontWeight = FontWeight.Normal,
 | 
			
		||||
                    fontSize = 10.sp,
 | 
			
		||||
                    lineHeight = 14.sp,
 | 
			
		||||
                ).toSpanStyle(),
 | 
			
		||||
                propertyValue = robotoMono.caption.copy(
 | 
			
		||||
                    fontWeight = FontWeight.Normal,
 | 
			
		||||
                    fontSize = 12.sp,
 | 
			
		||||
                    lineHeight = 14.sp,
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue