Refactor the camera scene (camera) system.
This commit is contained in:
parent
ce05e6a4c4
commit
3485b8a9fd
14 changed files with 453 additions and 298 deletions
|
|
@ -4,25 +4,16 @@ import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawWithCache
|
import androidx.compose.ui.draw.drawWithCache
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.unit.IntOffset
|
|
||||||
import androidx.compose.ui.unit.IntSize
|
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.Camera
|
||||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.onCameraControl
|
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.SceneElement
|
||||||
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneLayer
|
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneLayer
|
||||||
import com.pixelized.desktop.lwa.ui.composable.scene.utils.local
|
|
||||||
import org.jetbrains.compose.resources.DrawableResource
|
|
||||||
import org.jetbrains.compose.resources.imageResource
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class Scene(
|
data class Scene(
|
||||||
|
|
@ -43,12 +34,12 @@ fun Scene(
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
content: @Composable BoxScope.() -> Unit,
|
content: @Composable BoxScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
rememberCoroutineScope()
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.graphicsLayer { clip = true }
|
.graphicsLayer { clip = true }
|
||||||
.onCameraControl(scope = scope, sceneSize = scene.size, camera = camera)
|
.onCameraControl(sceneSize = scene.size, camera = camera)
|
||||||
.drawLayers(camera = camera, layers = scene.layers)
|
.drawLayers(camera = camera, layers = scene.layers)
|
||||||
.drawElements(camera = camera, elements = scene.elements)
|
.drawElements(camera = camera, elements = scene.elements)
|
||||||
.then(other = modifier),
|
.then(other = modifier),
|
||||||
|
|
@ -57,48 +48,6 @@ fun Scene(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Stable
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
fun Modifier.drawLayers(
|
fun Modifier.drawLayers(
|
||||||
camera: Camera,
|
camera: Camera,
|
||||||
|
|
@ -130,12 +79,6 @@ fun Modifier.drawElements(
|
||||||
dstSize = camera.cameraSize,
|
dstSize = camera.cameraSize,
|
||||||
alpha = element.alpha,
|
alpha = element.alpha,
|
||||||
)
|
)
|
||||||
drawRect(
|
|
||||||
color = Color.Red,
|
|
||||||
topLeft = element.position.local(camera = camera),
|
|
||||||
size = (element.size).toSize() / camera.zoom,
|
|
||||||
style = Stroke(width = 2f),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
package com.pixelized.desktop.lwa.ui.composable.scene.camera
|
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.Stable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.util.fastRoundToInt
|
import androidx.compose.ui.util.fastRoundToInt
|
||||||
|
|
@ -17,22 +16,11 @@ class Camera(
|
||||||
private val initialZoom: Float = 2f,
|
private val initialZoom: Float = 2f,
|
||||||
private val initialOffset: IntOffset = IntOffset.Zero,
|
private val initialOffset: IntOffset = IntOffset.Zero,
|
||||||
) {
|
) {
|
||||||
private var _zoom = Animatable(
|
private var _zoom = mutableStateOf(initialZoom)
|
||||||
initialValue = initialZoom,
|
val zoom: Float by _zoom
|
||||||
typeConverter = Float.VectorConverter,
|
|
||||||
)
|
|
||||||
val zoom: Float get() = _zoom.value
|
|
||||||
|
|
||||||
private var _offset = Animatable(
|
private var _offset = mutableStateOf(initialOffset)
|
||||||
initialValue = initialOffset,
|
val offset: IntOffset by _offset
|
||||||
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 _sceneSize: IntSize by mutableStateOf(IntSize.Zero)
|
||||||
private var _cameraSize: IntSize by mutableStateOf(IntSize.Zero)
|
private var _cameraSize: IntSize by mutableStateOf(IntSize.Zero)
|
||||||
|
|
@ -52,46 +40,48 @@ class Camera(
|
||||||
_sceneSize = sceneSize
|
_sceneSize = sceneSize
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun handlePanning(
|
fun handlePanning(
|
||||||
delta: Offset,
|
delta: Offset,
|
||||||
snap: Boolean,
|
|
||||||
) {
|
) {
|
||||||
val value = _offset.value - IntOffset(
|
val value = _offset.value - IntOffset(
|
||||||
x = (delta.x * zoom).fastRoundToInt(),
|
x = (delta.x * zoom).fastRoundToInt(),
|
||||||
y = (delta.y * zoom).fastRoundToInt(),
|
y = (delta.y * zoom).fastRoundToInt(),
|
||||||
)
|
)
|
||||||
when {
|
_offset.value = value
|
||||||
snap -> _offset.snapTo(targetValue = value)
|
|
||||||
else -> _offset.animateTo(targetValue = value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun handleZoom(
|
fun handleZoom(
|
||||||
power: Float,
|
power: Float,
|
||||||
snap: Boolean = false,
|
target: IntOffset? = null,
|
||||||
) {
|
) {
|
||||||
val value = _zoom.value * (1f - power)
|
val zoomTarget = _zoom.value * (1f - power)
|
||||||
when {
|
|
||||||
snap -> _zoom.snapTo(targetValue = value)
|
val projection = cameraSizeProjection(
|
||||||
else -> _zoom.animateTo(targetValue = value)
|
zoomTarget = zoomTarget,
|
||||||
}
|
)
|
||||||
|
val targetDelta = Offset(
|
||||||
|
x = target?.x?.toFloat()?.div(_cameraSize.width.toFloat()) ?: 0.5f,
|
||||||
|
y = target?.y?.toFloat()?.div(_cameraSize.height.toFloat()) ?: 0.5f,
|
||||||
|
)
|
||||||
|
val offsetTarget = _offset.value + IntOffset(
|
||||||
|
x = (targetDelta.x * projection.width).fastRoundToInt(),
|
||||||
|
y = (targetDelta.y * projection.height).fastRoundToInt(),
|
||||||
|
)
|
||||||
|
|
||||||
|
_zoom.value = zoomTarget
|
||||||
|
_offset.value = offsetTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun resetPosition(
|
fun resetPosition() {
|
||||||
snap: Boolean = false,
|
_offset.value = initialOffset
|
||||||
) {
|
|
||||||
when (snap) {
|
|
||||||
true -> _offset.snapTo(targetValue = initialOffset)
|
|
||||||
else -> _offset.animateTo(targetValue = initialOffset)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun resetZoom(
|
fun resetZoom() {
|
||||||
snap: Boolean = false,
|
_zoom.value = initialZoom
|
||||||
) {
|
|
||||||
when (snap) {
|
|
||||||
true -> _zoom.snapTo(targetValue = initialZoom)
|
|
||||||
else -> _zoom.animateTo(targetValue = initialZoom)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun cameraSizeProjection(zoomTarget: Float): Size = Size(
|
||||||
|
width = cameraSize.width * _zoom.value - cameraSize.width * zoomTarget,
|
||||||
|
height = cameraSize.height * _zoom.value - cameraSize.height * zoomTarget,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -12,13 +12,11 @@ import androidx.compose.ui.input.pointer.isTertiaryPressed
|
||||||
import androidx.compose.ui.input.pointer.onPointerEvent
|
import androidx.compose.ui.input.pointer.onPointerEvent
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import androidx.compose.ui.unit.round
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlin.math.sign
|
import kotlin.math.sign
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
||||||
fun Modifier.onCameraControl(
|
fun Modifier.onCameraControl(
|
||||||
scope: CoroutineScope,
|
|
||||||
sceneSize: IntSize,
|
sceneSize: IntSize,
|
||||||
camera: Camera,
|
camera: Camera,
|
||||||
): Modifier {
|
): Modifier {
|
||||||
|
|
@ -31,26 +29,22 @@ fun Modifier.onCameraControl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.onPointerEvent(PointerEventType.Move) { event: PointerEvent ->
|
.onPointerEvent(PointerEventType.Move) { event: PointerEvent ->
|
||||||
scope.launch {
|
offsetDelta.handlePositionChange(
|
||||||
offsetDelta.handlePositionChange(
|
event = event,
|
||||||
event = event,
|
) { delta ->
|
||||||
) { delta ->
|
when {
|
||||||
when {
|
event.buttons.isTertiaryPressed || (event.keyboardModifiers.isCtrlPressed && event.buttons.isPrimaryPressed) -> camera.handlePanning(
|
||||||
event.buttons.isTertiaryPressed || (event.keyboardModifiers.isCtrlPressed && event.buttons.isPrimaryPressed) -> camera.handlePanning(
|
delta = delta,
|
||||||
delta = delta,
|
)
|
||||||
snap = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onPointerEvent(PointerEventType.Scroll) { event: PointerEvent ->
|
.onPointerEvent(PointerEventType.Scroll) { event: PointerEvent ->
|
||||||
scope.launch {
|
val change = event.changes.first()
|
||||||
camera.handleZoom(
|
camera.handleZoom(
|
||||||
power = -event.changes.first().scrollDelta.y.sign * 0.15f,
|
power = -change.scrollDelta.y.sign * 0.15f,
|
||||||
snap = false,
|
target = change.position.round(),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,10 +53,10 @@ private data class CursorDelta(
|
||||||
var previousPosition: Offset = Offset.Zero,
|
var previousPosition: Offset = Offset.Zero,
|
||||||
var currentPosition: Offset = Offset.Zero,
|
var currentPosition: Offset = Offset.Zero,
|
||||||
) {
|
) {
|
||||||
suspend inline fun handlePositionChange(
|
inline fun handlePositionChange(
|
||||||
event: PointerEvent,
|
event: PointerEvent,
|
||||||
delay: Float = 10f,
|
delay: Float = 10f,
|
||||||
crossinline block: suspend (delta: Offset) -> Unit,
|
crossinline block: (delta: Offset) -> Unit,
|
||||||
) {
|
) {
|
||||||
val currentTimestamp = System.currentTimeMillis()
|
val currentTimestamp = System.currentTimeMillis()
|
||||||
if (currentTimestamp - lastDeltaTimestamp > delay) {
|
if (currentTimestamp - lastDeltaTimestamp > delay) {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@ import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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.composable.scene.cursor.Cursor
|
import com.pixelized.desktop.lwa.ui.composable.scene.cursor.Cursor
|
||||||
|
import com.pixelized.desktop.lwa.ui.composable.scene.utils.local
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
|
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
|
||||||
|
|
||||||
|
|
@ -26,6 +28,7 @@ fun CursorDebugPanel(
|
||||||
style: LwaTypography.Debug = MaterialTheme.lwa.typography.debug,
|
style: LwaTypography.Debug = MaterialTheme.lwa.typography.debug,
|
||||||
padding: Dp = MaterialTheme.lwa.dimen.debug.offset,
|
padding: Dp = MaterialTheme.lwa.dimen.debug.offset,
|
||||||
spacing: Dp = 2.dp,
|
spacing: Dp = 2.dp,
|
||||||
|
camera: Camera,
|
||||||
cursors: List<Cursor>,
|
cursors: List<Cursor>,
|
||||||
isOpen: Boolean = true,
|
isOpen: Boolean = true,
|
||||||
) {
|
) {
|
||||||
|
|
@ -50,10 +53,18 @@ fun CursorDebugPanel(
|
||||||
modifier = Modifier.padding(start = padding),
|
modifier = Modifier.padding(start = padding),
|
||||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||||
text = buildAnnotatedString {
|
text = buildAnnotatedString {
|
||||||
withStyle(style.propertyIdSpan) { append("coordinate: ") }
|
withStyle(style.propertyIdSpan) { append("global: ") }
|
||||||
append(cursor.offset.toString())
|
append(cursor.offset.toString())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = padding),
|
||||||
|
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
withStyle(style.propertyIdSpan) { append("local: ") }
|
||||||
|
append(cursor.offset.local(camera).toString())
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.composable.scene.debug
|
||||||
|
|
||||||
|
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.drawables.SceneElement
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ElementDebugPanel(
|
||||||
|
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.propertyId,
|
||||||
|
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.propertyIdSpan) { append("id: ") }
|
||||||
|
append(element.id)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = padding),
|
||||||
|
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
withStyle(style.propertyIdSpan) { append("offset: ") }
|
||||||
|
append(element.offset.toString())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = padding),
|
||||||
|
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
withStyle(style.propertyIdSpan) { append("size: ") }
|
||||||
|
append(element.size.toString())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = padding),
|
||||||
|
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
withStyle(style.propertyIdSpan) { append("alpha: ") }
|
||||||
|
append(element.alpha.toString())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.composable.scene.debug
|
||||||
|
|
||||||
|
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.drawables.SceneLayer
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LayerDebugPanel(
|
||||||
|
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.propertyId,
|
||||||
|
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.propertyIdSpan) { append("id: ") }
|
||||||
|
append(layer.id)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = padding),
|
||||||
|
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
withStyle(style.propertyIdSpan) { append("offset: ") }
|
||||||
|
append(layer.offset.toString())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = padding),
|
||||||
|
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
withStyle(style.propertyIdSpan) { append("size: ") }
|
||||||
|
append(layer.size.toString())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = padding),
|
||||||
|
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
withStyle(style.propertyIdSpan) { append("alpha: ") }
|
||||||
|
append(layer.alpha.toString())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,8 +17,6 @@ import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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.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
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
|
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
|
||||||
|
|
||||||
|
|
@ -63,7 +61,7 @@ fun SceneDebugPanel(
|
||||||
text = "Layers:(${scene.layers.size})"
|
text = "Layers:(${scene.layers.size})"
|
||||||
)
|
)
|
||||||
scene.layers.forEach { layer ->
|
scene.layers.forEach { layer ->
|
||||||
SceneLayerDebug(
|
LayerDebugPanel(
|
||||||
modifier = Modifier.padding(start = padding),
|
modifier = Modifier.padding(start = padding),
|
||||||
isOpen = false,
|
isOpen = false,
|
||||||
layer = layer,
|
layer = layer,
|
||||||
|
|
@ -78,7 +76,7 @@ fun SceneDebugPanel(
|
||||||
text = "Elements:(${scene.elements.size})"
|
text = "Elements:(${scene.elements.size})"
|
||||||
)
|
)
|
||||||
scene.elements.forEach { element ->
|
scene.elements.forEach { element ->
|
||||||
SceneElementDebug(
|
ElementDebugPanel(
|
||||||
modifier = Modifier.padding(start = padding),
|
modifier = Modifier.padding(start = padding),
|
||||||
isOpen = false,
|
isOpen = false,
|
||||||
element = element,
|
element = element,
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,21 @@
|
||||||
package com.pixelized.desktop.lwa.ui.composable.scene.drawables
|
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.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
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.IntOffset
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import org.jetbrains.compose.resources.DrawableResource
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import org.jetbrains.compose.resources.imageResource
|
||||||
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
|
import java.util.UUID
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class SceneElement(
|
class SceneElement(
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
texture: ImageBitmap,
|
texture: ImageBitmap,
|
||||||
offset: IntOffset = IntOffset.Companion.Zero,
|
offset: IntOffset = IntOffset.Zero,
|
||||||
size: IntSize = IntSize(texture.width, texture.height),
|
size: IntSize = IntSize(texture.width, texture.height),
|
||||||
alpha: Float = 1f,
|
alpha: Float = 1f,
|
||||||
) : SceneDrawable(
|
) : SceneDrawable(
|
||||||
|
|
@ -45,60 +33,22 @@ class SceneElement(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SceneElementDebug(
|
@Stable
|
||||||
modifier: Modifier = Modifier,
|
fun rememberElementFromResource(
|
||||||
element: SceneElement,
|
name: String,
|
||||||
isOpen: Boolean = true,
|
resource: DrawableResource,
|
||||||
style: LwaTypography.Debug = MaterialTheme.lwa.typography.debug,
|
offset: IntOffset = IntOffset.Zero,
|
||||||
padding: Dp = MaterialTheme.lwa.dimen.debug.offset,
|
): SceneElement {
|
||||||
) {
|
val texture = imageResource(
|
||||||
val isOpen = remember { mutableStateOf(isOpen) }
|
resource = resource,
|
||||||
|
)
|
||||||
Column(
|
return remember(resource) {
|
||||||
modifier = Modifier
|
SceneElement(
|
||||||
.clickable { isOpen.value = isOpen.value.not() }
|
id = UUID.randomUUID().toString(),
|
||||||
.widthIn(min = MaterialTheme.lwa.dimen.debug.panelWidth)
|
name = name,
|
||||||
.then(other = modifier),
|
texture = texture,
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
|
offset = offset,
|
||||||
) {
|
alpha = 1f,
|
||||||
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.propertyIdSpan) { append("id: ") }
|
|
||||||
append(element.id)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(start = padding),
|
|
||||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
|
||||||
text = buildAnnotatedString {
|
|
||||||
withStyle(style.propertyIdSpan) { append("offset: ") }
|
|
||||||
append(element.offset.toString())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(start = padding),
|
|
||||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
|
||||||
text = buildAnnotatedString {
|
|
||||||
withStyle(style.propertyIdSpan) { append("size: ") }
|
|
||||||
append(element.size.toString())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(start = padding),
|
|
||||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
|
||||||
text = buildAnnotatedString {
|
|
||||||
withStyle(style.propertyIdSpan) { append("alpha: ") }
|
|
||||||
append(element.alpha.toString())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,33 +1,21 @@
|
||||||
package com.pixelized.desktop.lwa.ui.composable.scene.drawables
|
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.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
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.IntOffset
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import org.jetbrains.compose.resources.DrawableResource
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import org.jetbrains.compose.resources.imageResource
|
||||||
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
|
import java.util.UUID
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class SceneLayer(
|
class SceneLayer(
|
||||||
id : String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
texture: ImageBitmap,
|
texture: ImageBitmap,
|
||||||
offset: IntOffset = IntOffset.Companion.Zero,
|
offset: IntOffset = IntOffset.Zero,
|
||||||
size: IntSize = IntSize(texture.width, texture.height),
|
size: IntSize = IntSize(texture.width, texture.height),
|
||||||
alpha: Float = 1f,
|
alpha: Float = 1f,
|
||||||
) : SceneDrawable(
|
) : SceneDrawable(
|
||||||
|
|
@ -40,60 +28,22 @@ class SceneLayer(
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SceneLayerDebug(
|
@Stable
|
||||||
modifier: Modifier = Modifier,
|
fun rememberLayoutFromResource(
|
||||||
layer: SceneLayer,
|
name: String,
|
||||||
isOpen: Boolean = true,
|
resource: DrawableResource,
|
||||||
style: LwaTypography.Debug = MaterialTheme.lwa.typography.debug,
|
offset: IntOffset = IntOffset.Zero,
|
||||||
padding: Dp = MaterialTheme.lwa.dimen.debug.offset,
|
): SceneLayer {
|
||||||
) {
|
val texture = imageResource(
|
||||||
val isOpen = remember { mutableStateOf(isOpen) }
|
resource = resource,
|
||||||
|
)
|
||||||
Column(
|
return remember(resource) {
|
||||||
modifier = Modifier
|
SceneLayer(
|
||||||
.clickable { isOpen.value = isOpen.value.not() }
|
id = UUID.randomUUID().toString(),
|
||||||
.widthIn(min = MaterialTheme.lwa.dimen.debug.panelWidth)
|
name = name,
|
||||||
.then(other = modifier),
|
texture = texture,
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
|
offset = offset,
|
||||||
) {
|
alpha = 1f,
|
||||||
Text(
|
|
||||||
style = MaterialTheme.lwa.typography.debug.propertyId,
|
|
||||||
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.propertyIdSpan) { append("id: ") }
|
|
||||||
append(layer.id)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(start = padding),
|
|
||||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
|
||||||
text = buildAnnotatedString {
|
|
||||||
withStyle(style.propertyIdSpan) { append("offset: ") }
|
|
||||||
append(layer.offset.toString())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(start = padding),
|
|
||||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
|
||||||
text = buildAnnotatedString {
|
|
||||||
withStyle(style.propertyIdSpan) { append("size: ") }
|
|
||||||
append(layer.size.toString())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(start = padding),
|
|
||||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
|
||||||
text = buildAnnotatedString {
|
|
||||||
withStyle(style.propertyIdSpan) { append("alpha: ") }
|
|
||||||
append(layer.alpha.toString())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.pixelized.desktop.lwa.ui.composable.scene.fogOfWar
|
package com.pixelized.desktop.lwa.ui.composable.scene.fogOfWar
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.neverEqualPolicy
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawWithCache
|
import androidx.compose.ui.draw.drawWithCache
|
||||||
|
|
@ -28,14 +30,14 @@ import kotlinx.coroutines.launch
|
||||||
class FogOfWar(
|
class FogOfWar(
|
||||||
val color: Color = Color.Black.copy(alpha = 0.5f),
|
val color: Color = Color.Black.copy(alpha = 0.5f),
|
||||||
) {
|
) {
|
||||||
val path = Path()
|
val path = mutableStateOf(value = Path(), policy = neverEqualPolicy())
|
||||||
|
|
||||||
fun moveTo(position: Offset) {
|
fun moveTo(position: Offset) {
|
||||||
path.moveTo(x = position.x, y = position.y)
|
path.value = path.value.also { it.moveTo(x = position.x, y = position.y) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun lineTo(position: Offset) {
|
fun lineTo(position: Offset) {
|
||||||
path.lineTo(x = position.x, y = position.y)
|
path.value = path.value.also { it.lineTo(x = position.x, y = position.y) }
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ fun Modifier.drawFogOfWar(
|
||||||
top = -camera.offset.y.toFloat(),
|
top = -camera.offset.y.toFloat(),
|
||||||
) {
|
) {
|
||||||
drawPath(
|
drawPath(
|
||||||
path = fogOfWar.path,
|
path = fogOfWar.path.value,
|
||||||
style = stroke,
|
style = stroke,
|
||||||
color = color,
|
color = color,
|
||||||
blendMode = BlendMode.Clear,
|
blendMode = BlendMode.Clear,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.composable.scene.utils
|
||||||
|
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.compose.ui.util.fastRoundToInt
|
||||||
|
import com.pixelized.desktop.lwa.ui.composable.scene.camera.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 IntOffset.global(
|
||||||
|
camera: Camera,
|
||||||
|
): Offset = global(
|
||||||
|
zoom = camera.zoom,
|
||||||
|
offset = camera.offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 IntOffset.global(
|
||||||
|
zoom: Float,
|
||||||
|
offset: IntOffset,
|
||||||
|
): Offset = Offset(
|
||||||
|
x = this.x * zoom + offset.x,
|
||||||
|
y = this.y * zoom + 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 IntOffset.local(
|
||||||
|
camera: Camera,
|
||||||
|
): Offset = local(
|
||||||
|
zoom = camera.zoom,
|
||||||
|
offset = camera.offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(
|
||||||
|
zoom: Float,
|
||||||
|
offset: IntOffset,
|
||||||
|
): Offset = Offset(
|
||||||
|
x = (this.x - offset.x) / zoom,
|
||||||
|
y = (this.y - offset.y) / zoom,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 IntOffset.globalInt(
|
||||||
|
camera: Camera,
|
||||||
|
): IntOffset = globalInt(
|
||||||
|
zoom = camera.zoom,
|
||||||
|
offset = camera.offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 IntOffset.globalInt(
|
||||||
|
zoom: Float,
|
||||||
|
offset: IntOffset,
|
||||||
|
): IntOffset = IntOffset(
|
||||||
|
x = (this.x * zoom + offset.x).fastRoundToInt(),
|
||||||
|
y = (this.y * zoom + offset.y).fastRoundToInt(),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.localInt(
|
||||||
|
camera: Camera,
|
||||||
|
): IntOffset = localInt(
|
||||||
|
zoom = camera.zoom,
|
||||||
|
offset = camera.offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.localInt(
|
||||||
|
zoom: Float,
|
||||||
|
offset: IntOffset,
|
||||||
|
): IntOffset = IntOffset(
|
||||||
|
x = ((this.x - offset.x) / zoom).fastRoundToInt(),
|
||||||
|
y = ((this.y - offset.y) / zoom).fastRoundToInt(),
|
||||||
|
)
|
||||||
|
|
@ -2,6 +2,7 @@ package com.pixelized.desktop.lwa.ui.composable.scene.utils
|
||||||
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.compose.ui.util.fastRoundToInt
|
||||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -11,9 +12,22 @@ import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||||
*/
|
*/
|
||||||
fun Offset.global(
|
fun Offset.global(
|
||||||
camera: Camera,
|
camera: Camera,
|
||||||
|
): Offset = global(
|
||||||
|
zoom = camera.zoom,
|
||||||
|
offset = camera.offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(
|
||||||
|
zoom: Float,
|
||||||
|
offset: IntOffset,
|
||||||
): Offset = Offset(
|
): Offset = Offset(
|
||||||
x = this.x * camera.zoom + camera.offset.x,
|
x = this.x * zoom + offset.x,
|
||||||
y = this.y * camera.zoom + camera.offset.y,
|
y = this.y * zoom + offset.y,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -22,18 +36,67 @@ fun Offset.global(
|
||||||
*/
|
*/
|
||||||
fun Offset.local(
|
fun Offset.local(
|
||||||
camera: Camera,
|
camera: Camera,
|
||||||
): Offset = Offset(
|
): Offset = local(
|
||||||
x = (this.x - camera.offset.x) / camera.zoom,
|
zoom = camera.zoom,
|
||||||
y = (this.y - camera.offset.y) / camera.zoom,
|
offset = camera.offset,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert global positon to local one.
|
* Convert global positon to local one.
|
||||||
* Local position take into account the camera and are use to display stuff on the Scene composable.
|
* Local position take into account the camera and are use to display stuff on the Scene composable.
|
||||||
*/
|
*/
|
||||||
fun IntOffset.local(
|
fun Offset.local(
|
||||||
camera: Camera,
|
zoom: Float,
|
||||||
|
offset: IntOffset,
|
||||||
): Offset = Offset(
|
): Offset = Offset(
|
||||||
x = (this.x.toFloat() - camera.offset.x) / camera.zoom,
|
x = (this.x - offset.x) / zoom,
|
||||||
y = (this.y.toFloat() - camera.offset.y) / camera.zoom,
|
y = (this.y - offset.y) / zoom,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.globalInt(
|
||||||
|
camera: Camera,
|
||||||
|
): IntOffset = globalInt(
|
||||||
|
zoom = camera.zoom,
|
||||||
|
offset = camera.offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.globalInt(
|
||||||
|
zoom: Float,
|
||||||
|
offset: IntOffset,
|
||||||
|
): IntOffset = IntOffset(
|
||||||
|
x = (this.x * zoom + offset.x).fastRoundToInt(),
|
||||||
|
y = (this.y * zoom + offset.y).fastRoundToInt(),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.localInt(
|
||||||
|
camera: Camera,
|
||||||
|
): IntOffset = localInt(
|
||||||
|
zoom = camera.zoom,
|
||||||
|
offset = camera.offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.localInt(
|
||||||
|
zoom: Float,
|
||||||
|
offset: IntOffset,
|
||||||
|
): IntOffset = IntOffset(
|
||||||
|
x = ((this.x - offset.x) / zoom).fastRoundToInt(),
|
||||||
|
y = ((this.y - offset.y) / zoom).fastRoundToInt(),
|
||||||
)
|
)
|
||||||
|
|
@ -19,7 +19,6 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.pixelized.desktop.lwa.ui.composable.scene.Scene
|
import com.pixelized.desktop.lwa.ui.composable.scene.Scene
|
||||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||||
import com.pixelized.desktop.lwa.ui.composable.scene.cursor.Cursor
|
import com.pixelized.desktop.lwa.ui.composable.scene.cursor.Cursor
|
||||||
|
|
@ -27,7 +26,7 @@ import com.pixelized.desktop.lwa.ui.composable.scene.cursor.onCursorControl
|
||||||
import com.pixelized.desktop.lwa.ui.composable.scene.debug.CameraDebugPanel
|
import com.pixelized.desktop.lwa.ui.composable.scene.debug.CameraDebugPanel
|
||||||
import com.pixelized.desktop.lwa.ui.composable.scene.debug.CursorDebugPanel
|
import com.pixelized.desktop.lwa.ui.composable.scene.debug.CursorDebugPanel
|
||||||
import com.pixelized.desktop.lwa.ui.composable.scene.debug.SceneDebugPanel
|
import com.pixelized.desktop.lwa.ui.composable.scene.debug.SceneDebugPanel
|
||||||
import com.pixelized.desktop.lwa.ui.composable.scene.rememberLayoutFromResource
|
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.rememberLayoutFromResource
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
|
@ -58,7 +57,7 @@ fun MapScene(
|
||||||
val camera = remember {
|
val camera = remember {
|
||||||
Camera(
|
Camera(
|
||||||
initialZoom = 1f,
|
initialZoom = 1f,
|
||||||
initialOffset = IntOffset(x = -150, y = -120),
|
initialOffset = IntOffset(x = 1407, y = 1520),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val cursor = remember {
|
val cursor = remember {
|
||||||
|
|
@ -86,22 +85,14 @@ fun MapScene(
|
||||||
.padding(paddingValues = MaterialTheme.lwa.dimen.paddingValues),
|
.padding(paddingValues = MaterialTheme.lwa.dimen.paddingValues),
|
||||||
enableDebug = enableDebug,
|
enableDebug = enableDebug,
|
||||||
onZoomIn = {
|
onZoomIn = {
|
||||||
scope.launch {
|
camera.handleZoom(power = 0.15f)
|
||||||
camera.handleZoom(power = 0.3f)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onZoomOut = {
|
onZoomOut = {
|
||||||
scope.launch {
|
camera.handleZoom(power = -0.15f)
|
||||||
camera.handleZoom(power = -0.3f)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onResetCamera = {
|
onResetCamera = {
|
||||||
scope.launch {
|
camera.resetPosition()
|
||||||
camera.resetPosition()
|
camera.resetZoom()
|
||||||
}
|
|
||||||
scope.launch {
|
|
||||||
camera.resetZoom()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onToggleLayer = {
|
onToggleLayer = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
|
@ -136,6 +127,7 @@ fun MapScene(
|
||||||
camera = camera,
|
camera = camera,
|
||||||
)
|
)
|
||||||
CursorDebugPanel(
|
CursorDebugPanel(
|
||||||
|
camera = camera,
|
||||||
cursors = remember { listOf(cursor) },
|
cursors = remember { listOf(cursor) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue