Add MapView into the App
This commit is contained in:
parent
3d5f29c18c
commit
d648b8a05e
36 changed files with 867 additions and 745 deletions
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M480,760Q546,760 593,713Q640,666 640,600L640,440Q640,374 593,327Q546,280 480,280Q414,280 367,327Q320,374 320,440L320,600Q320,666 367,713Q414,760 480,760ZM400,640L560,640L560,560L400,560L400,640ZM400,480L560,480L560,400L400,400L400,480ZM480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520ZM480,840Q415,840 359.5,808Q304,776 272,720L160,720L160,640L244,640Q241,620 240.5,600Q240,580 240,560L160,560L160,480L240,480Q240,460 240.5,440Q241,420 244,400L160,400L160,320L272,320Q286,297 303.5,277Q321,257 344,242L280,176L336,120L422,206Q450,197 479,197Q508,197 536,206L624,120L680,176L614,242Q637,257 655.5,276.5Q674,296 688,320L800,320L800,400L716,400Q719,420 719.5,440Q720,460 720,480L800,480L800,560L720,560Q720,580 719.5,600Q719,620 716,640L800,640L800,720L688,720Q656,776 600.5,808Q545,840 480,840ZM40,240L40,120Q40,87 63.5,63.5Q87,40 120,40L240,40L240,120L120,120Q120,120 120,120Q120,120 120,120L120,240L40,240ZM240,920L120,920Q87,920 63.5,896.5Q40,873 40,840L40,720L120,720L120,840Q120,840 120,840Q120,840 120,840L240,840L240,920ZM720,920L720,840L840,840Q840,840 840,840Q840,840 840,840L840,720L920,720L920,840Q920,873 896.5,896.5Q873,920 840,920L720,920ZM840,240L840,120Q840,120 840,120Q840,120 840,120L720,120L720,40L840,40Q873,40 896.5,63.5Q920,87 920,120L920,240L840,240Z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M600,840L360,756L174,828Q154,836 137,823.5Q120,811 120,790L120,230Q120,217 127.5,207Q135,197 148,192L360,120L600,204L786,132Q806,124 823,136.5Q840,149 840,170L840,730Q840,743 832.5,753Q825,763 812,768L600,840ZM560,742L560,274L400,218L400,686L560,742ZM640,742L760,702L760,228L640,274L640,742ZM200,732L320,686L320,218L200,258L200,732ZM640,274L640,274L640,742L640,742L640,274ZM320,218L320,218L320,686L320,686L320,218Z" />
|
||||
</vector>
|
||||
|
|
@ -41,8 +41,8 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.Characte
|
|||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.NpcRibbonViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbonViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.TextMessageFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.CampaignChatViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.TextMessageFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.links.ResourcesViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.network.NetworkFactory
|
||||
|
|
|
|||
|
|
@ -1,177 +0,0 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.VectorConverter
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastRoundToInt
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneLayer
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
|
||||
|
||||
@Stable
|
||||
class Camera(
|
||||
private val initialZoom: Float = 2f,
|
||||
private val initialOffset: IntOffset = IntOffset.Zero,
|
||||
) {
|
||||
private var _zoom = Animatable(
|
||||
initialValue = initialZoom,
|
||||
typeConverter = Float.VectorConverter,
|
||||
)
|
||||
val zoom: Float get() = _zoom.value
|
||||
|
||||
private var _offset = Animatable(
|
||||
initialValue = initialOffset,
|
||||
typeConverter = IntOffset.VectorConverter,
|
||||
)
|
||||
val offset: IntOffset by derivedStateOf {
|
||||
_offset.value + IntOffset(
|
||||
x = (_sceneSize.width - cameraSizeZoomed.width) / 2,
|
||||
y = (_sceneSize.height - cameraSizeZoomed.height) / 2,
|
||||
)
|
||||
}
|
||||
|
||||
private var _sceneSize: IntSize by mutableStateOf(IntSize.Zero)
|
||||
private var _cameraSize: IntSize by mutableStateOf(IntSize.Zero)
|
||||
val cameraSize: IntSize get() = _cameraSize
|
||||
val cameraSizeZoomed: IntSize by derivedStateOf {
|
||||
IntSize(
|
||||
width = (cameraSize.width * zoom).fastRoundToInt(),
|
||||
height = (cameraSize.height * zoom).fastRoundToInt(),
|
||||
)
|
||||
}
|
||||
|
||||
fun changeSizes(
|
||||
sceneSize: IntSize,
|
||||
cameraSize: IntSize,
|
||||
) {
|
||||
_cameraSize = cameraSize
|
||||
_sceneSize = sceneSize
|
||||
}
|
||||
|
||||
suspend fun handlePanning(
|
||||
delta: Offset,
|
||||
snap: Boolean,
|
||||
) {
|
||||
val value = _offset.value - IntOffset(
|
||||
x = (delta.x * zoom).fastRoundToInt(),
|
||||
y = (delta.y * zoom).fastRoundToInt(),
|
||||
)
|
||||
when {
|
||||
snap -> _offset.snapTo(targetValue = value)
|
||||
else -> _offset.animateTo(targetValue = value)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun handleZoom(
|
||||
zoomIn: Boolean,
|
||||
power: Float,
|
||||
snap: Boolean = false,
|
||||
) {
|
||||
val value = _zoom.value * when {
|
||||
zoomIn -> 1f - power
|
||||
else -> 1f + power
|
||||
}
|
||||
when {
|
||||
snap -> _zoom.snapTo(targetValue = value)
|
||||
else -> _zoom.animateTo(targetValue = value)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun resetPosition(
|
||||
snap: Boolean = false,
|
||||
) {
|
||||
when (snap) {
|
||||
true -> _offset.snapTo(targetValue = initialOffset)
|
||||
else -> _offset.animateTo(targetValue = initialOffset)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun resetZoom(
|
||||
snap: Boolean = false,
|
||||
) {
|
||||
when (snap) {
|
||||
true -> _zoom.snapTo(targetValue = initialZoom)
|
||||
else -> _zoom.animateTo(targetValue = initialZoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SceneCameraDebug(
|
||||
modifier: Modifier = Modifier,
|
||||
camera: Camera,
|
||||
isOpen: Boolean = true,
|
||||
style: LwaTypography.Debug = MaterialTheme.lwa.typography.debug,
|
||||
padding: Dp = MaterialTheme.lwa.dimen.debug.offset,
|
||||
) {
|
||||
val isOpen = remember { mutableStateOf(isOpen) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clickable { isOpen.value = isOpen.value.not() }
|
||||
.widthIn(min = MaterialTheme.lwa.dimen.debug.panelWidth)
|
||||
.then(other = modifier),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.debug.title,
|
||||
color = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
text = "Camera",
|
||||
)
|
||||
if (isOpen.value) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("offset: ") }
|
||||
append(camera.offset.toString())
|
||||
},
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("size: ") }
|
||||
append(camera.cameraSize.toString())
|
||||
},
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("zoom: ") }
|
||||
append(camera.zoom.toString())
|
||||
},
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("size: ") }
|
||||
append(camera.cameraSizeZoomed.toString())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.LocalCampaignLayoutScope
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_center_focus_weak_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_foggy_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_foggy_filled_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_zoom_in_map_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_zoom_out_map_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.icon_d100
|
||||
import lwacharactersheet.composeapp.generated.resources.image_dahome_maps
|
||||
import lwacharactersheet.composeapp.generated.resources.image_dahome_regions
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
|
||||
@Composable
|
||||
fun MapScene(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val campaign = LocalCampaignLayoutScope.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val fogOfWarEdit = remember { mutableStateOf(true) }
|
||||
|
||||
val map = rememberLayoutFromResource(
|
||||
name = "Dahomé",
|
||||
resource = Res.drawable.image_dahome_maps,
|
||||
)
|
||||
val mapRegionOverlay = rememberLayoutFromResource(
|
||||
name = "Région",
|
||||
resource = Res.drawable.image_dahome_regions,
|
||||
)
|
||||
val element1 = rememberElementFromResource(
|
||||
name = "Group",
|
||||
resource = Res.drawable.icon_d100,
|
||||
offset = IntOffset(
|
||||
x = 2128,
|
||||
y = 1875,
|
||||
),
|
||||
)
|
||||
val element2 = rememberElementFromResource(
|
||||
name = "End",
|
||||
resource = Res.drawable.icon_d100,
|
||||
offset = IntOffset(
|
||||
x = map.size.width,
|
||||
y = map.size.height,
|
||||
)
|
||||
)
|
||||
val element3 = rememberElementFromResource(
|
||||
name = "Start",
|
||||
resource = Res.drawable.icon_d100,
|
||||
offset = IntOffset(
|
||||
x = 0,
|
||||
y = 0,
|
||||
)
|
||||
)
|
||||
val scene = remember(map, mapRegionOverlay, element1, element2) {
|
||||
Scene(
|
||||
camera = Camera(
|
||||
initialZoom = 1f,
|
||||
initialOffset = IntOffset(x = -150, y = -120),
|
||||
),
|
||||
fogOfWar = FogOfWar(),
|
||||
layers = listOf(
|
||||
map,
|
||||
mapRegionOverlay,
|
||||
),
|
||||
elements = listOf(
|
||||
element1,
|
||||
element2,
|
||||
element3,
|
||||
),
|
||||
)
|
||||
}
|
||||
Scene(
|
||||
modifier = modifier.onFogOfWarControl(
|
||||
scope = scope,
|
||||
enable = fogOfWarEdit.value,
|
||||
fogOfWar = scene.fogOfWar,
|
||||
camera = scene.camera,
|
||||
),
|
||||
scene = scene,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.BottomStart)
|
||||
.padding(
|
||||
start = campaign.leftPanel.value.width,
|
||||
bottom = campaign.chatOverlay.value.height,
|
||||
)
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
scene.camera.handleZoom(
|
||||
zoomIn = true,
|
||||
power = 0.3f,
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_zoom_in_map_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
scene.camera.handleZoom(
|
||||
zoomIn = false,
|
||||
power = 0.3f,
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_zoom_out_map_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
scene.camera.resetPosition()
|
||||
}
|
||||
scope.launch {
|
||||
scene.camera.resetZoom()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_center_focus_weak_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
scene.layers.getOrNull(1)?.let {
|
||||
it.alpha(alpha = if (it.alpha == 0f) 1f else 0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_visibility_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
fogOfWarEdit.value = fogOfWarEdit.value.not()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = when (fogOfWarEdit.value) {
|
||||
true -> painterResource(Res.drawable.ic_foggy_filled_24dp)
|
||||
else -> painterResource(Res.drawable.ic_foggy_24dp)
|
||||
},
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +1,31 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.CompositingStrategy
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.StrokeJoin
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.scale
|
||||
import androidx.compose.ui.graphics.drawscope.translate
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.PointerEvent
|
||||
import androidx.compose.ui.input.pointer.PointerEventType
|
||||
import androidx.compose.ui.input.pointer.isAltPressed
|
||||
import androidx.compose.ui.input.pointer.isCtrlPressed
|
||||
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
||||
import androidx.compose.ui.input.pointer.isTertiaryPressed
|
||||
import androidx.compose.ui.input.pointer.onPointerEvent
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.toSize
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.onCameraControl
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneElement
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneLayer
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.utils.global
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.utils.local
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.DrawableResource
|
||||
import org.jetbrains.compose.resources.imageResource
|
||||
import java.util.UUID
|
||||
import kotlin.math.sign
|
||||
|
||||
@Stable
|
||||
data class Scene(
|
||||
val camera: Camera,
|
||||
val fogOfWar: FogOfWar,
|
||||
val layers: List<SceneLayer>,
|
||||
val elements: List<SceneElement>,
|
||||
) {
|
||||
|
|
@ -64,35 +39,21 @@ data class Scene(
|
|||
@Composable
|
||||
fun Scene(
|
||||
modifier: Modifier = Modifier,
|
||||
camera: Camera,
|
||||
scene: Scene,
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val cursors = remember {
|
||||
listOf(
|
||||
Cursor()
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
modifier = Modifier
|
||||
.graphicsLayer { clip = true }
|
||||
.onCameraControl(scope = scope, scene = scene)
|
||||
.onCursorControl(camera = scene.camera, cursor = cursors.first())
|
||||
.drawLayers(camera = scene.camera, layers = scene.layers)
|
||||
.drawElements(camera = scene.camera, elements = scene.elements)
|
||||
.drawCursors(camera = scene.camera, cursors = cursors)
|
||||
.drawFogOfWar(scene = scene),
|
||||
.onCameraControl(scope = scope, sceneSize = scene.size, camera = camera)
|
||||
.drawLayers(camera = camera, layers = scene.layers)
|
||||
.drawElements(camera = camera, elements = scene.elements)
|
||||
.then(other = modifier),
|
||||
) {
|
||||
content()
|
||||
|
||||
SceneDebugPanel(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.TopEnd)
|
||||
.padding(paddingValues = MaterialTheme.lwa.dimen.paddingValues),
|
||||
cursor = cursors.first(),
|
||||
scene = scene,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -138,83 +99,6 @@ fun rememberElementFromResource(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
||||
fun Modifier.onCameraControl(
|
||||
scope: CoroutineScope,
|
||||
scene: Scene,
|
||||
): Modifier {
|
||||
val offsetDelta = CursorDelta()
|
||||
return this
|
||||
.onSizeChanged {
|
||||
scene.camera.changeSizes(
|
||||
sceneSize = scene.size,
|
||||
cameraSize = it,
|
||||
)
|
||||
}
|
||||
.onPointerEvent(PointerEventType.Move) { event: PointerEvent ->
|
||||
scope.launch {
|
||||
offsetDelta.handlePositionChange(
|
||||
event = event,
|
||||
) { delta ->
|
||||
when {
|
||||
event.buttons.isTertiaryPressed || (event.keyboardModifiers.isCtrlPressed && event.buttons.isPrimaryPressed) -> scene.camera.handlePanning(
|
||||
delta = delta,
|
||||
snap = true,
|
||||
)
|
||||
|
||||
event.keyboardModifiers.isAltPressed -> scene.camera.handleZoom(
|
||||
zoomIn = delta.y.sign < 0f,
|
||||
power = 0.025f,
|
||||
snap = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onPointerEvent(PointerEventType.Scroll) { event: PointerEvent ->
|
||||
scope.launch {
|
||||
scene.camera.handleZoom(
|
||||
zoomIn = event.changes.first().scrollDelta.y.sign < 0f,
|
||||
power = 0.15f,
|
||||
snap = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
fun Modifier.onCursorControl(
|
||||
camera: Camera,
|
||||
cursor: Cursor,
|
||||
): Modifier = this
|
||||
.onPointerEvent(PointerEventType.Exit) { event: PointerEvent ->
|
||||
cursor.change(
|
||||
position = Offset.Unspecified,
|
||||
)
|
||||
}
|
||||
.onPointerEvent(PointerEventType.Move) { event: PointerEvent ->
|
||||
cursor.change(
|
||||
position = event.changes.first().position.global(camera = camera),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Modifier.drawCursors(
|
||||
camera: Camera,
|
||||
cursors: List<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,
|
||||
|
|
@ -254,61 +138,4 @@ fun Modifier.drawElements(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Modifier.drawFogOfWar(
|
||||
scene: Scene,
|
||||
): Modifier = this
|
||||
.graphicsLayer(
|
||||
compositingStrategy = CompositingStrategy.Offscreen
|
||||
)
|
||||
.drawWithCache {
|
||||
val stroke = Stroke(
|
||||
width = 32f,
|
||||
cap = StrokeCap.Round,
|
||||
join = StrokeJoin.Round
|
||||
)
|
||||
val fog = Color.Black.copy(alpha = 0.5f)
|
||||
val color = Color.Transparent
|
||||
onDrawBehind {
|
||||
drawRect(
|
||||
color = fog,
|
||||
)
|
||||
scale(
|
||||
scale = 1 / scene.camera.zoom,
|
||||
pivot = Offset.Zero,
|
||||
) {
|
||||
translate(
|
||||
left = -scene.camera.offset.x.toFloat(),
|
||||
top = -scene.camera.offset.y.toFloat(),
|
||||
) {
|
||||
drawPath(
|
||||
path = scene.fogOfWar.path,
|
||||
style = stroke,
|
||||
color = color,
|
||||
blendMode = BlendMode.Clear,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class CursorDelta(
|
||||
var lastDeltaTimestamp: Long = System.currentTimeMillis(),
|
||||
var previousPosition: Offset = Offset.Zero,
|
||||
var currentPosition: Offset = Offset.Zero,
|
||||
) {
|
||||
suspend inline fun handlePositionChange(
|
||||
event: PointerEvent,
|
||||
delay: Float = 10f,
|
||||
crossinline block: suspend (delta: Offset) -> Unit,
|
||||
) {
|
||||
val currentTimestamp = System.currentTimeMillis()
|
||||
if (currentTimestamp - lastDeltaTimestamp > delay) {
|
||||
lastDeltaTimestamp = currentTimestamp
|
||||
previousPosition = currentPosition
|
||||
currentPosition = event.changes.first().position
|
||||
block(currentPosition - previousPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene.camera
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.VectorConverter
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.util.fastRoundToInt
|
||||
|
||||
@Stable
|
||||
class Camera(
|
||||
private val initialZoom: Float = 2f,
|
||||
private val initialOffset: IntOffset = IntOffset.Zero,
|
||||
) {
|
||||
private var _zoom = Animatable(
|
||||
initialValue = initialZoom,
|
||||
typeConverter = Float.VectorConverter,
|
||||
)
|
||||
val zoom: Float get() = _zoom.value
|
||||
|
||||
private var _offset = Animatable(
|
||||
initialValue = initialOffset,
|
||||
typeConverter = IntOffset.VectorConverter,
|
||||
)
|
||||
val offset: IntOffset by derivedStateOf {
|
||||
_offset.value + IntOffset(
|
||||
x = (_sceneSize.width - cameraSizeZoomed.width) / 2,
|
||||
y = (_sceneSize.height - cameraSizeZoomed.height) / 2,
|
||||
)
|
||||
}
|
||||
|
||||
private var _sceneSize: IntSize by mutableStateOf(IntSize.Zero)
|
||||
private var _cameraSize: IntSize by mutableStateOf(IntSize.Zero)
|
||||
val cameraSize: IntSize get() = _cameraSize
|
||||
val cameraSizeZoomed: IntSize by derivedStateOf {
|
||||
IntSize(
|
||||
width = (cameraSize.width * zoom).fastRoundToInt(),
|
||||
height = (cameraSize.height * zoom).fastRoundToInt(),
|
||||
)
|
||||
}
|
||||
|
||||
fun changeSizes(
|
||||
sceneSize: IntSize,
|
||||
cameraSize: IntSize,
|
||||
) {
|
||||
_cameraSize = cameraSize
|
||||
_sceneSize = sceneSize
|
||||
}
|
||||
|
||||
suspend fun handlePanning(
|
||||
delta: Offset,
|
||||
snap: Boolean,
|
||||
) {
|
||||
val value = _offset.value - IntOffset(
|
||||
x = (delta.x * zoom).fastRoundToInt(),
|
||||
y = (delta.y * zoom).fastRoundToInt(),
|
||||
)
|
||||
when {
|
||||
snap -> _offset.snapTo(targetValue = value)
|
||||
else -> _offset.animateTo(targetValue = value)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun handleZoom(
|
||||
power: Float,
|
||||
snap: Boolean = false,
|
||||
) {
|
||||
val value = _zoom.value * (1f - power)
|
||||
when {
|
||||
snap -> _zoom.snapTo(targetValue = value)
|
||||
else -> _zoom.animateTo(targetValue = value)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun resetPosition(
|
||||
snap: Boolean = false,
|
||||
) {
|
||||
when (snap) {
|
||||
true -> _offset.snapTo(targetValue = initialOffset)
|
||||
else -> _offset.animateTo(targetValue = initialOffset)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun resetZoom(
|
||||
snap: Boolean = false,
|
||||
) {
|
||||
when (snap) {
|
||||
true -> _zoom.snapTo(targetValue = initialZoom)
|
||||
else -> _zoom.animateTo(targetValue = initialZoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene.camera
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.pointer.PointerEvent
|
||||
import androidx.compose.ui.input.pointer.PointerEventType
|
||||
import androidx.compose.ui.input.pointer.isCtrlPressed
|
||||
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
||||
import androidx.compose.ui.input.pointer.isTertiaryPressed
|
||||
import androidx.compose.ui.input.pointer.onPointerEvent
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.sign
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
||||
fun Modifier.onCameraControl(
|
||||
scope: CoroutineScope,
|
||||
sceneSize: IntSize,
|
||||
camera: Camera,
|
||||
): Modifier {
|
||||
val offsetDelta = CursorDelta()
|
||||
return this
|
||||
.onSizeChanged {
|
||||
camera.changeSizes(
|
||||
sceneSize = sceneSize,
|
||||
cameraSize = it,
|
||||
)
|
||||
}
|
||||
.onPointerEvent(PointerEventType.Move) { event: PointerEvent ->
|
||||
scope.launch {
|
||||
offsetDelta.handlePositionChange(
|
||||
event = event,
|
||||
) { delta ->
|
||||
when {
|
||||
event.buttons.isTertiaryPressed || (event.keyboardModifiers.isCtrlPressed && event.buttons.isPrimaryPressed) -> camera.handlePanning(
|
||||
delta = delta,
|
||||
snap = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onPointerEvent(PointerEventType.Scroll) { event: PointerEvent ->
|
||||
scope.launch {
|
||||
camera.handleZoom(
|
||||
power = -event.changes.first().scrollDelta.y.sign * 0.15f,
|
||||
snap = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class CursorDelta(
|
||||
var lastDeltaTimestamp: Long = System.currentTimeMillis(),
|
||||
var previousPosition: Offset = Offset.Zero,
|
||||
var currentPosition: Offset = Offset.Zero,
|
||||
) {
|
||||
suspend inline fun handlePositionChange(
|
||||
event: PointerEvent,
|
||||
delay: Float = 10f,
|
||||
crossinline block: suspend (delta: Offset) -> Unit,
|
||||
) {
|
||||
val currentTimestamp = System.currentTimeMillis()
|
||||
if (currentTimestamp - lastDeltaTimestamp > delay) {
|
||||
lastDeltaTimestamp = currentTimestamp
|
||||
previousPosition = currentPosition
|
||||
currentPosition = event.changes.first().position
|
||||
block(currentPosition - previousPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene.cursor
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
|
||||
@Stable
|
||||
class Cursor(
|
||||
initial: IntOffset = IntOffset.Zero,
|
||||
) {
|
||||
private val _offset = mutableStateOf(initial)
|
||||
val offset by _offset
|
||||
|
||||
fun change(
|
||||
position: Offset,
|
||||
) {
|
||||
_offset.value = IntOffset(
|
||||
x = position.x.toInt(),
|
||||
y = position.y.toInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene.cursor
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.utils.local
|
||||
|
||||
fun Modifier.drawCursors(
|
||||
camera: Camera,
|
||||
cursors: List<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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene.cursor
|
||||
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.pointer.PointerEvent
|
||||
import androidx.compose.ui.input.pointer.PointerEventType
|
||||
import androidx.compose.ui.input.pointer.onPointerEvent
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.utils.global
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
fun Modifier.onCursorControl(
|
||||
camera: Camera,
|
||||
cursor: Cursor,
|
||||
): Modifier = this
|
||||
.onPointerEvent(PointerEventType.Exit) { event: PointerEvent ->
|
||||
cursor.change(
|
||||
position = Offset.Unspecified,
|
||||
)
|
||||
}
|
||||
.onPointerEvent(PointerEventType.Move) { event: PointerEvent ->
|
||||
cursor.change(
|
||||
position = event.changes.first().position.global(camera = camera),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene.debug
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
|
||||
|
||||
@Composable
|
||||
fun CameraDebugPanel(
|
||||
modifier: Modifier = Modifier,
|
||||
style: LwaTypography.Debug = MaterialTheme.lwa.typography.debug,
|
||||
padding: Dp = MaterialTheme.lwa.dimen.debug.offset,
|
||||
spacing: Dp = 2.dp,
|
||||
camera: Camera,
|
||||
isOpen: Boolean = true,
|
||||
) {
|
||||
val isOpen = remember { mutableStateOf(isOpen) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clickable { isOpen.value = isOpen.value.not() }
|
||||
.widthIn(min = MaterialTheme.lwa.dimen.debug.panelWidth)
|
||||
.animateContentSize()
|
||||
.then(other = modifier),
|
||||
verticalArrangement = Arrangement.spacedBy(space = spacing),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.debug.title,
|
||||
color = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
text = "Camera",
|
||||
)
|
||||
if (isOpen.value) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyIdSpan) { append("offset: ") }
|
||||
append(camera.offset.toString())
|
||||
},
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyIdSpan) { append("size: ") }
|
||||
append(camera.cameraSize.toString())
|
||||
},
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyIdSpan) { append("zoom: ") }
|
||||
append(camera.zoom.toString())
|
||||
},
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyIdSpan) { append("projection: ") }
|
||||
append(camera.cameraSizeZoomed.toString())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||
package com.pixelized.desktop.lwa.ui.composable.scene.debug
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -8,44 +9,25 @@ import androidx.compose.foundation.layout.widthIn
|
|||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.cursor.Cursor
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
|
||||
|
||||
@Stable
|
||||
class Cursor(
|
||||
initial: IntOffset = IntOffset.Zero,
|
||||
) {
|
||||
private val _offset = mutableStateOf(initial)
|
||||
val offset by _offset
|
||||
|
||||
fun change(
|
||||
position: Offset,
|
||||
) {
|
||||
_offset.value = IntOffset(
|
||||
x = position.x.toInt(),
|
||||
y = position.y.toInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SceneCursorDebug(
|
||||
fun CursorDebugPanel(
|
||||
modifier: Modifier = Modifier,
|
||||
cursor: Cursor,
|
||||
isOpen: Boolean = true,
|
||||
style: LwaTypography.Debug = MaterialTheme.lwa.typography.debug,
|
||||
padding: Dp = MaterialTheme.lwa.dimen.debug.offset,
|
||||
spacing: Dp = 2.dp,
|
||||
cursors: List<Cursor>,
|
||||
isOpen: Boolean = true,
|
||||
) {
|
||||
val isOpen = remember { mutableStateOf(isOpen) }
|
||||
|
||||
|
|
@ -53,8 +35,9 @@ fun SceneCursorDebug(
|
|||
modifier = Modifier
|
||||
.clickable { isOpen.value = isOpen.value.not() }
|
||||
.widthIn(min = MaterialTheme.lwa.dimen.debug.panelWidth)
|
||||
.animateContentSize()
|
||||
.then(other = modifier),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(space = spacing),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.debug.title,
|
||||
|
|
@ -62,14 +45,16 @@ fun SceneCursorDebug(
|
|||
text = "Cursor",
|
||||
)
|
||||
if (isOpen.value) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("offset: ") }
|
||||
append(cursor.offset.toString())
|
||||
},
|
||||
)
|
||||
cursors.forEach { cursor ->
|
||||
Text(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyIdSpan) { append("coordinate: ") }
|
||||
append(cursor.offset.toString())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,11 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||
package com.pixelized.desktop.lwa.ui.composable.scene.debug
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -20,6 +16,7 @@ import androidx.compose.ui.text.buildAnnotatedString
|
|||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.Scene
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneElementDebug
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneLayerDebug
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
|
|
@ -28,74 +25,11 @@ import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
|
|||
@Composable
|
||||
fun SceneDebugPanel(
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = MaterialTheme.lwa.dimen.paddingValues,
|
||||
cursor: Cursor,
|
||||
scene: Scene,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.verticalScroll(state = rememberScrollState())
|
||||
.then(other = modifier),
|
||||
backgroundColor = MaterialTheme.lwa.colorScheme.elevated.base4dp,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(paddingValues = paddingValues),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
SceneDebug(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
scene = scene,
|
||||
)
|
||||
SceneCursorDebug(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
cursor = cursor,
|
||||
)
|
||||
SceneCameraDebug(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
camera = scene.camera,
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.debug.title,
|
||||
text = "Layers:(${scene.layers.size})"
|
||||
)
|
||||
scene.layers.forEach { layer ->
|
||||
SceneLayerDebug(
|
||||
modifier = Modifier
|
||||
.animateContentSize()
|
||||
.padding(start = MaterialTheme.lwa.dimen.debug.offset),
|
||||
isOpen = false,
|
||||
layer = layer,
|
||||
)
|
||||
}
|
||||
}
|
||||
Column {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.debug.title,
|
||||
text = "Elements:(${scene.elements.size})"
|
||||
)
|
||||
scene.elements.forEach { element ->
|
||||
SceneElementDebug(
|
||||
modifier = Modifier
|
||||
.animateContentSize()
|
||||
.padding(start = MaterialTheme.lwa.dimen.debug.offset),
|
||||
isOpen = false,
|
||||
element = element,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SceneDebug(
|
||||
modifier: Modifier = Modifier,
|
||||
scene: Scene,
|
||||
isOpen: Boolean = true,
|
||||
style: LwaTypography.Debug = MaterialTheme.lwa.typography.debug,
|
||||
padding: Dp = MaterialTheme.lwa.dimen.debug.offset,
|
||||
spacing: Dp = 2.dp,
|
||||
scene: Scene,
|
||||
isOpen: Boolean = true,
|
||||
) {
|
||||
val isOpen = remember { mutableStateOf(isOpen) }
|
||||
|
||||
|
|
@ -103,8 +37,9 @@ private fun SceneDebug(
|
|||
modifier = Modifier
|
||||
.clickable { isOpen.value = isOpen.value.not() }
|
||||
.widthIn(min = MaterialTheme.lwa.dimen.debug.panelWidth)
|
||||
.animateContentSize()
|
||||
.then(other = modifier),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(space = spacing),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.debug.title,
|
||||
|
|
@ -116,10 +51,40 @@ private fun SceneDebug(
|
|||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("Size: ") }
|
||||
withStyle(style.propertyIdSpan) { append("Size: ") }
|
||||
append(scene.size.toString())
|
||||
},
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.debug.propertyId,
|
||||
text = "Layers:(${scene.layers.size})"
|
||||
)
|
||||
scene.layers.forEach { layer ->
|
||||
SceneLayerDebug(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
isOpen = false,
|
||||
layer = layer,
|
||||
)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.debug.propertyId,
|
||||
text = "Elements:(${scene.elements.size})"
|
||||
)
|
||||
scene.elements.forEach { element ->
|
||||
SceneElementDebug(
|
||||
modifier = Modifier.padding(start = padding),
|
||||
isOpen = false,
|
||||
element = element,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ fun SceneElementDebug(
|
|||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("id: ") }
|
||||
withStyle(style.propertyIdSpan) { append("id: ") }
|
||||
append(element.id)
|
||||
},
|
||||
)
|
||||
|
|
@ -79,7 +79,7 @@ fun SceneElementDebug(
|
|||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("offset: ") }
|
||||
withStyle(style.propertyIdSpan) { append("offset: ") }
|
||||
append(element.offset.toString())
|
||||
},
|
||||
)
|
||||
|
|
@ -87,7 +87,7 @@ fun SceneElementDebug(
|
|||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("size: ") }
|
||||
withStyle(style.propertyIdSpan) { append("size: ") }
|
||||
append(element.size.toString())
|
||||
},
|
||||
)
|
||||
|
|
@ -95,7 +95,7 @@ fun SceneElementDebug(
|
|||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("alpha: ") }
|
||||
withStyle(style.propertyIdSpan) { append("alpha: ") }
|
||||
append(element.alpha.toString())
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ fun SceneLayerDebug(
|
|||
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.debug.title,
|
||||
style = MaterialTheme.lwa.typography.debug.propertyId,
|
||||
color = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
text = layer.name,
|
||||
)
|
||||
|
|
@ -66,7 +66,7 @@ fun SceneLayerDebug(
|
|||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("id: ") }
|
||||
withStyle(style.propertyIdSpan) { append("id: ") }
|
||||
append(layer.id)
|
||||
},
|
||||
)
|
||||
|
|
@ -74,7 +74,7 @@ fun SceneLayerDebug(
|
|||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("offset: ") }
|
||||
withStyle(style.propertyIdSpan) { append("offset: ") }
|
||||
append(layer.offset.toString())
|
||||
},
|
||||
)
|
||||
|
|
@ -82,7 +82,7 @@ fun SceneLayerDebug(
|
|||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("size: ") }
|
||||
withStyle(style.propertyIdSpan) { append("size: ") }
|
||||
append(layer.size.toString())
|
||||
},
|
||||
)
|
||||
|
|
@ -90,7 +90,7 @@ fun SceneLayerDebug(
|
|||
modifier = Modifier.padding(start = padding),
|
||||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyId) { append("alpha: ") }
|
||||
withStyle(style.propertyIdSpan) { append("alpha: ") }
|
||||
append(layer.alpha.toString())
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene.fogOfWar
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.CompositingStrategy
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.StrokeJoin
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.scale
|
||||
import androidx.compose.ui.graphics.drawscope.translate
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.PointerEvent
|
||||
import androidx.compose.ui.input.pointer.PointerEventType
|
||||
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
||||
import androidx.compose.ui.input.pointer.onPointerEvent
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.utils.global
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
class FogOfWar(
|
||||
val color: Color = Color.Black.copy(alpha = 0.5f),
|
||||
) {
|
||||
val path = Path()
|
||||
|
||||
fun moveTo(position: Offset) {
|
||||
path.moveTo(x = position.x, y = position.y)
|
||||
}
|
||||
|
||||
fun lineTo(position: Offset) {
|
||||
path.lineTo(x = position.x, y = position.y)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val NONE = FogOfWar(color = Color.Transparent)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene.fogOfWar
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.CompositingStrategy
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.StrokeJoin
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.scale
|
||||
import androidx.compose.ui.graphics.drawscope.translate
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||
|
||||
fun Modifier.drawFogOfWar(
|
||||
camera: Camera,
|
||||
fogOfWar: FogOfWar,
|
||||
): Modifier = this
|
||||
.graphicsLayer(
|
||||
compositingStrategy = CompositingStrategy.Offscreen
|
||||
)
|
||||
.drawWithCache {
|
||||
val stroke = Stroke(
|
||||
width = 32f,
|
||||
cap = StrokeCap.Round,
|
||||
join = StrokeJoin.Round
|
||||
)
|
||||
val fog = Color.Black.copy(alpha = 0.5f)
|
||||
val color = Color.Transparent
|
||||
onDrawBehind {
|
||||
drawRect(
|
||||
color = fog,
|
||||
)
|
||||
scale(
|
||||
scale = 1 / camera.zoom,
|
||||
pivot = Offset.Zero,
|
||||
) {
|
||||
translate(
|
||||
left = -camera.offset.x.toFloat(),
|
||||
top = -camera.offset.y.toFloat(),
|
||||
) {
|
||||
drawPath(
|
||||
path = fogOfWar.path,
|
||||
style = stroke,
|
||||
color = color,
|
||||
blendMode = BlendMode.Clear,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +1,16 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||
package com.pixelized.desktop.lwa.ui.composable.scene.fogOfWar
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.input.pointer.PointerEvent
|
||||
import androidx.compose.ui.input.pointer.PointerEventType
|
||||
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
||||
import androidx.compose.ui.input.pointer.onPointerEvent
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.utils.global
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
class FogOfWar(
|
||||
val color: Color = Color.Black.copy(alpha = 0.5f),
|
||||
) {
|
||||
val path = Path()
|
||||
|
||||
fun moveTo(position: Offset) {
|
||||
path.moveTo(x = position.x, y = position.y)
|
||||
}
|
||||
|
||||
fun lineTo(position: Offset) {
|
||||
path.lineTo(x = position.x, y = position.y)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val NONE = FogOfWar(color = Color.Transparent)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
fun Modifier.onFogOfWarControl(
|
||||
scope: CoroutineScope,
|
||||
|
|
@ -46,7 +24,7 @@ fun Modifier.onFogOfWarControl(
|
|||
var previousEvent: PointerEvent? = null
|
||||
|
||||
this
|
||||
.onPointerEvent(PointerEventType.Release) { event: PointerEvent ->
|
||||
.onPointerEvent(PointerEventType.Release) { _: PointerEvent ->
|
||||
scope.launch {
|
||||
println("PointerEventType.Release")
|
||||
lastEventTime = System.currentTimeMillis()
|
||||
|
|
@ -2,7 +2,7 @@ package com.pixelized.desktop.lwa.ui.composable.scene.utils
|
|||
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.Camera
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||
|
||||
/**
|
||||
* Convert local positon to global one.
|
||||
|
|
@ -34,7 +34,7 @@ class WindowController(
|
|||
private val _windows = mutableStateOf<Map<String, Window>>(emptyMap())
|
||||
val windows: State<Map<String, Window>> get() = _windows
|
||||
|
||||
fun showWindow(window: Window) {
|
||||
fun openWindow(window: Window) {
|
||||
_windows.value = _windows.value.toMutableMap().apply { this[window.id] = window }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ class GameMasterWindow(
|
|||
size = size,
|
||||
)
|
||||
|
||||
fun WindowController.navigateToGameMasterWindow(
|
||||
fun WindowController.openGameMasterWindow(
|
||||
title: String = "Game master",
|
||||
) {
|
||||
showWindow(
|
||||
openWindow(
|
||||
window = GameMasterWindow(
|
||||
title = title,
|
||||
size = DpSize(
|
||||
|
|
|
|||
|
|
@ -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.MapScene
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.map.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
|
||||
|
|
@ -55,8 +55,8 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransi
|
|||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbon
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.NpcRibbonViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbonViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChat
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.CampaignChat
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.CampaignChatViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbar
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
|
|
@ -101,9 +101,7 @@ fun CampaignScreen(
|
|||
|
||||
},
|
||||
main = {
|
||||
MapScene(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
)
|
||||
|
||||
},
|
||||
chat = {
|
||||
CampaignChat(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.text
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.chatbox
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
|
|
@ -42,15 +42,15 @@ import androidx.compose.ui.window.WindowState
|
|||
import com.pixelized.desktop.lwa.ui.navigation.window.LocalWindowState
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignLayoutScope
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.LocalCampaignLayoutScope
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.CharacteristicTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.CharacteristicTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.DiminishedTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.DiminishedTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.PurseTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.PurseTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.TextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.CharacteristicTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.CharacteristicTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.DiminishedTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.DiminishedTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.PurseTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.PurseTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.RollTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.RollTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.TextMessage
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import com.pixelized.desktop.lwa.usecase.SettingsUseCase
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.text
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.chatbox
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.text
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.chatbox
|
||||
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.model.Settings
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.CharacteristicTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.DiminishedTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.PurseTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.TextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.CharacteristicTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.DiminishedTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.PurseTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.RollTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.TextMessage
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.text.messages
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.text.messages
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.text.messages
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.TooltipPlacement
|
||||
|
|
@ -8,7 +8,6 @@ import androidx.compose.material.MaterialTheme
|
|||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.text.messages
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.text.messages
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages
|
||||
|
||||
sealed interface TextMessage {
|
||||
val id : String
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.map
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
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.cursor.Cursor
|
||||
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.CursorDebugPanel
|
||||
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.theme.lwa
|
||||
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_frame_bug_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.image_dahome_maps
|
||||
import lwacharactersheet.composeapp.generated.resources.image_dahome_regions
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
|
||||
@Composable
|
||||
fun MapScene(
|
||||
modifier: Modifier = Modifier,
|
||||
enableDebug: Boolean,
|
||||
) {
|
||||
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 camera = remember {
|
||||
Camera(
|
||||
initialZoom = 1f,
|
||||
initialOffset = IntOffset(x = -150, y = -120),
|
||||
)
|
||||
}
|
||||
val cursor = remember {
|
||||
Cursor()
|
||||
}
|
||||
val scene = remember(map, mapRegionOverlay) {
|
||||
Scene(
|
||||
layers = listOf(map, mapRegionOverlay),
|
||||
elements = emptyList(),
|
||||
)
|
||||
}
|
||||
val openDebugMenu = remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
Scene(
|
||||
modifier = Modifier
|
||||
.onCursorControl(camera = camera, cursor = cursor)
|
||||
.then(other = modifier),
|
||||
scene = scene,
|
||||
camera = camera,
|
||||
) {
|
||||
MapActions(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.BottomEnd)
|
||||
.padding(paddingValues = MaterialTheme.lwa.dimen.paddingValues),
|
||||
enableDebug = enableDebug,
|
||||
onZoomIn = {
|
||||
scope.launch {
|
||||
camera.handleZoom(power = 0.3f)
|
||||
}
|
||||
},
|
||||
onZoomOut = {
|
||||
scope.launch {
|
||||
camera.handleZoom(power = -0.3f)
|
||||
}
|
||||
},
|
||||
onResetCamera = {
|
||||
scope.launch {
|
||||
camera.resetPosition()
|
||||
}
|
||||
scope.launch {
|
||||
camera.resetZoom()
|
||||
}
|
||||
},
|
||||
onToggleLayer = {
|
||||
scope.launch {
|
||||
scene.layers.getOrNull(1)?.let {
|
||||
it.alpha(alpha = if (it.alpha == 0f) 1f else 0f)
|
||||
}
|
||||
}
|
||||
},
|
||||
onToggleDebug = {
|
||||
openDebugMenu.value = openDebugMenu.value.not()
|
||||
},
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.align(alignment = Alignment.TopEnd),
|
||||
visible = openDebugMenu.value,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues = MaterialTheme.lwa.dimen.paddingValues)
|
||||
.verticalScroll(state = rememberScrollState()),
|
||||
backgroundColor = MaterialTheme.lwa.colorScheme.elevated.base4dp,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(paddingValues = MaterialTheme.lwa.dimen.paddingValues),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
SceneDebugPanel(
|
||||
scene = scene,
|
||||
)
|
||||
CameraDebugPanel(
|
||||
camera = camera,
|
||||
)
|
||||
CursorDebugPanel(
|
||||
cursors = remember { listOf(cursor) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MapActions(
|
||||
modifier: Modifier = Modifier,
|
||||
enableDebug: Boolean,
|
||||
onZoomIn: () -> Unit,
|
||||
onZoomOut: () -> Unit,
|
||||
onResetCamera: () -> Unit,
|
||||
onToggleLayer: () -> Unit,
|
||||
onToggleDebug: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onZoomIn,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_zoom_in_map_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = onZoomOut,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_zoom_out_map_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = onResetCamera,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_center_focus_weak_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = onToggleLayer,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_visibility_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = enableDebug,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onToggleDebug,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_frame_bug_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,25 +13,25 @@ import androidx.compose.material.Text
|
|||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.pixelized.desktop.lwa.LocalWindowController
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToSettings
|
||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToGameMasterWindow
|
||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.openGameMasterWindow
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.map.MapScene
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.links.ResourcesMenu
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.network.NetworkMenu
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_link_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_map_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_settings_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_wifi_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_wifi_off_24dp
|
||||
|
|
@ -41,9 +41,10 @@ import org.jetbrains.compose.resources.stringResource
|
|||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Stable
|
||||
data class MenuState(
|
||||
val isResourcesMenuOpen: MutableState<Boolean> = mutableStateOf(false),
|
||||
val isNetworkMenuOpen: MutableState<Boolean> = mutableStateOf(false),
|
||||
data class CampaignMenuStateUio(
|
||||
val isResourcesMenuOpen: Boolean,
|
||||
val isNetworkMenuOpen: Boolean,
|
||||
val isMapMenuOpen: Boolean,
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
@ -53,51 +54,47 @@ fun CampaignToolbar(
|
|||
val windows = LocalWindowController.current
|
||||
val screen = LocalScreenController.current
|
||||
|
||||
val menuState = remember { MenuState() }
|
||||
|
||||
val title = viewModel.title.collectAsState()
|
||||
val status = viewModel.status.collectAsState()
|
||||
val isAdmin = viewModel.isAdmin.collectAsState()
|
||||
val title = viewModel.title.collectAsStateWithLifecycle()
|
||||
val status = viewModel.status.collectAsStateWithLifecycle()
|
||||
val isAdmin = viewModel.isAdmin.collectAsStateWithLifecycle()
|
||||
val menusState = viewModel.menusState.collectAsStateWithLifecycle()
|
||||
|
||||
CampaignToolbarContent(
|
||||
title = title,
|
||||
status = status,
|
||||
isAdmin = isAdmin,
|
||||
state = menuState,
|
||||
menusState = menusState,
|
||||
onAdmin = {
|
||||
windows.navigateToGameMasterWindow()
|
||||
},
|
||||
onNetwork = {
|
||||
menuState.isNetworkMenuOpen.value = true
|
||||
},
|
||||
onResources = {
|
||||
menuState.isResourcesMenuOpen.value = true
|
||||
windows.openGameMasterWindow()
|
||||
},
|
||||
onSettings = {
|
||||
screen.navigateToSettings()
|
||||
},
|
||||
onDismissNetworkMenu = {
|
||||
menuState.isNetworkMenuOpen.value = false
|
||||
},
|
||||
onDismissResourcesMenu = {
|
||||
menuState.isResourcesMenuOpen.value = false
|
||||
},
|
||||
onNetwork = viewModel::onNetwork,
|
||||
onResources = viewModel::onResources,
|
||||
onMap = viewModel::onMap,
|
||||
onDismissNetworkMenu = viewModel::onDismissNetworkMenu,
|
||||
onDismissResourcesMenu = viewModel::onDismissResourcesMenu,
|
||||
onDismissMapMenu = viewModel::onDismissMapMenu,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
private fun CampaignToolbarContent(
|
||||
modifier: Modifier = Modifier,
|
||||
title: State<String>,
|
||||
status: State<NetworkRepository.Status>,
|
||||
isAdmin: State<Boolean>,
|
||||
state: MenuState,
|
||||
menusState: State<CampaignMenuStateUio>,
|
||||
onAdmin: () -> Unit,
|
||||
onNetwork: () -> Unit,
|
||||
onMap: () -> Unit,
|
||||
onResources: () -> Unit,
|
||||
onSettings: () -> Unit,
|
||||
onDismissNetworkMenu: () -> Unit,
|
||||
onDismissResourcesMenu: () -> Unit,
|
||||
onDismissMapMenu: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
|
|
@ -121,6 +118,25 @@ private fun CampaignToolbarContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
IconButton(
|
||||
onClick = onMap,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_map_24dp),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
DropdownMenu(
|
||||
expanded = menusState.value.isMapMenuOpen,
|
||||
onDismissRequest = onDismissMapMenu,
|
||||
content = {
|
||||
MapScene(
|
||||
modifier = Modifier.size(640.dp),
|
||||
enableDebug = isAdmin.value,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = onResources,
|
||||
) {
|
||||
|
|
@ -130,7 +146,7 @@ private fun CampaignToolbarContent(
|
|||
contentDescription = null,
|
||||
)
|
||||
DropdownMenu(
|
||||
expanded = state.isResourcesMenuOpen.value,
|
||||
expanded = menusState.value.isResourcesMenuOpen,
|
||||
onDismissRequest = onDismissResourcesMenu,
|
||||
content = {
|
||||
ResourcesMenu(
|
||||
|
|
@ -156,7 +172,7 @@ private fun CampaignToolbarContent(
|
|||
contentDescription = null,
|
||||
)
|
||||
DropdownMenu(
|
||||
expanded = state.isNetworkMenuOpen.value,
|
||||
expanded = menusState.value.isNetworkMenuOpen,
|
||||
onDismissRequest = onDismissNetworkMenu,
|
||||
content = {
|
||||
NetworkMenu(
|
||||
|
|
|
|||
|
|
@ -3,20 +3,30 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.toolbar
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
class CampaignToolbarViewModel(
|
||||
private val characterRepository: CharacterSheetRepository,
|
||||
private val campaignRepository: CampaignRepository,
|
||||
campaignRepository: CampaignRepository,
|
||||
networkRepository: NetworkRepository,
|
||||
settingsRepository: SettingsRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _menusState = MutableStateFlow(
|
||||
CampaignMenuStateUio(
|
||||
isResourcesMenuOpen = false,
|
||||
isNetworkMenuOpen = false,
|
||||
isMapMenuOpen = false
|
||||
)
|
||||
)
|
||||
val menusState: StateFlow<CampaignMenuStateUio> = _menusState
|
||||
|
||||
val status = networkRepository.status
|
||||
|
||||
val title = campaignRepository.campaignFlow()
|
||||
|
|
@ -34,4 +44,36 @@ class CampaignToolbarViewModel(
|
|||
started = SharingStarted.Lazily,
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
val isGameMaster = settingsRepository.settingsFlow()
|
||||
.map { it.isGameMaster ?: false }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
fun onNetwork() {
|
||||
_menusState.update { it.copy(isNetworkMenuOpen = true) }
|
||||
}
|
||||
|
||||
fun onResources() {
|
||||
_menusState.update { it.copy(isResourcesMenuOpen = true) }
|
||||
}
|
||||
|
||||
fun onMap() {
|
||||
_menusState.update { it.copy(isMapMenuOpen = true) }
|
||||
}
|
||||
|
||||
fun onDismissNetworkMenu() {
|
||||
_menusState.update { it.copy(isNetworkMenuOpen = false) }
|
||||
}
|
||||
|
||||
fun onDismissResourcesMenu() {
|
||||
_menusState.update { it.copy(isResourcesMenuOpen = false) }
|
||||
}
|
||||
|
||||
fun onDismissMapMenu() {
|
||||
_menusState.update { it.copy(isMapMenuOpen = false) }
|
||||
}
|
||||
}
|
||||
|
|
@ -79,7 +79,8 @@ data class LwaTypography(
|
|||
@Stable
|
||||
data class Debug(
|
||||
val title: TextStyle,
|
||||
val propertyId: SpanStyle,
|
||||
val propertyId: TextStyle,
|
||||
val propertyIdSpan: SpanStyle,
|
||||
val propertyValue: TextStyle,
|
||||
)
|
||||
}
|
||||
|
|
@ -216,6 +217,11 @@ fun lwaTypography(
|
|||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 10.sp,
|
||||
lineHeight = 14.sp,
|
||||
),
|
||||
propertyIdSpan = robotoMono.caption.copy(
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 10.sp,
|
||||
lineHeight = 14.sp,
|
||||
).toSpanStyle(),
|
||||
propertyValue = robotoMono.caption.copy(
|
||||
fontWeight = FontWeight.Normal,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue