Intial scene commit
This commit is contained in:
parent
fc06e3ef95
commit
76fc199d5e
20 changed files with 583 additions and 233 deletions
|
|
@ -34,6 +34,7 @@ kotlin {
|
||||||
// injection
|
// injection
|
||||||
implementation(libs.koin.compose)
|
implementation(libs.koin.compose)
|
||||||
implementation(libs.koin.compose.viewmodel)
|
implementation(libs.koin.compose.viewmodel)
|
||||||
|
implementation(libs.engawapg.zoomable)
|
||||||
// composable component.
|
// composable component.
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
implementation(libs.coil.network.ktor)
|
implementation(libs.coil.network.ktor)
|
||||||
|
|
|
||||||
|
|
@ -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,640Q414,640 367,593Q320,546 320,480Q320,414 367,367Q414,320 480,320Q546,320 593,367Q640,414 640,480Q640,546 593,593Q546,640 480,640ZM480,560Q513,560 536.5,536.5Q560,513 560,480Q560,447 536.5,423.5Q513,400 480,400Q447,400 423.5,423.5Q400,447 400,480Q400,513 423.5,536.5Q447,560 480,560ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,600L200,600L200,760Q200,760 200,760Q200,760 200,760L360,760L360,840L200,840ZM600,840L600,760L760,760Q760,760 760,760Q760,760 760,760L760,600L840,600L840,760Q840,793 816.5,816.5Q793,840 760,840L600,840ZM120,360L120,200Q120,167 143.5,143.5Q167,120 200,120L360,120L360,200L200,200Q200,200 200,200Q200,200 200,200L200,360L120,360ZM760,360L760,200Q760,200 760,200Q760,200 760,200L600,200L600,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,360L760,360Z" />
|
||||||
|
</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="M156,860L100,804L224,680L120,680L120,600L360,600L360,840L280,840L280,736L156,860ZM804,860L680,736L680,840L600,840L600,600L840,600L840,680L736,680L860,804L804,860ZM120,360L120,280L224,280L100,156L156,100L280,224L280,120L360,120L360,360L120,360ZM600,360L600,120L680,120L680,224L804,100L860,156L736,280L840,280L840,360L600,360Z" />
|
||||||
|
</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="M120,840L120,600L200,600L200,704L324,580L380,636L256,760L360,760L360,840L120,840ZM600,840L600,760L704,760L580,636L636,580L760,704L760,600L840,600L840,840L600,840ZM324,380L200,256L200,360L120,360L120,120L360,120L360,200L256,200L380,324L324,380ZM636,380L580,324L704,200L600,200L600,120L840,120L840,360L760,360L760,256L636,380Z" />
|
||||||
|
</vector>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 591 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
|
|
@ -40,8 +40,8 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.Characte
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderFactory
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.CharacterDetailInventoryFactory
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.CharacterDetailInventoryFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc.NpcRibbonViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.NpcRibbonViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel
|
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.CampaignChatViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.TextMessageFactory
|
import com.pixelized.desktop.lwa.ui.screen.campaign.text.TextMessageFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||||
|
|
||||||
|
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(
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class FogOfWar(
|
||||||
|
val color: Color = Color.Black.copy(alpha = 0.5f),
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.VectorConverter
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class Layout(
|
||||||
|
val texture: ImageBitmap,
|
||||||
|
val offset: IntOffset = IntOffset.Zero,
|
||||||
|
val size: IntSize = IntSize(texture.width, texture.height),
|
||||||
|
private val initialAlpha: Float = 1f,
|
||||||
|
) {
|
||||||
|
private val _alpha = Animatable(
|
||||||
|
initialValue = initialAlpha,
|
||||||
|
typeConverter = Float.VectorConverter,
|
||||||
|
)
|
||||||
|
val alpha get() = _alpha.value
|
||||||
|
|
||||||
|
suspend fun alpha(
|
||||||
|
alpha: Float,
|
||||||
|
snap: Boolean = false,
|
||||||
|
) {
|
||||||
|
when (snap) {
|
||||||
|
true -> _alpha.snapTo(targetValue = alpha)
|
||||||
|
else -> _alpha.animateTo(targetValue = alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun resetAlpha(
|
||||||
|
snap: Boolean = false,
|
||||||
|
) {
|
||||||
|
when (snap) {
|
||||||
|
true -> _alpha.snapTo(targetValue = initialAlpha)
|
||||||
|
else -> _alpha.animateTo(targetValue = initialAlpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,278 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.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.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.dp
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.campaign.LocalCampaignLayoutScope
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_center_focus_weak_24dp
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_zoom_in_map_24dp
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_zoom_out_map_24dp
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.icon_d100
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.image_dahome_maps
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.image_dahome_regions
|
||||||
|
import org.jetbrains.compose.resources.DrawableResource
|
||||||
|
import org.jetbrains.compose.resources.imageResource
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import kotlin.math.sign
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class Scene(
|
||||||
|
val camera: Camera,
|
||||||
|
val layouts: List<Layout>,
|
||||||
|
val fogOfWar: FogOfWar,
|
||||||
|
) {
|
||||||
|
val size: IntSize = IntSize(
|
||||||
|
width = layouts.maxOf { it.size.width },
|
||||||
|
height = layouts.maxOf { it.size.height },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Scene(
|
||||||
|
modifier: Modifier,
|
||||||
|
) {
|
||||||
|
val campaign = LocalCampaignLayoutScope.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val scene = rememberScene(
|
||||||
|
camera = Camera(
|
||||||
|
initialZoom = 1f,
|
||||||
|
initialOffset = IntOffset(x = -150, y = -120),
|
||||||
|
),
|
||||||
|
fogOfWar = FogOfWar(),
|
||||||
|
rememberLayoutFromResource(
|
||||||
|
resource = Res.drawable.image_dahome_maps,
|
||||||
|
),
|
||||||
|
rememberLayoutFromResource(
|
||||||
|
resource = Res.drawable.image_dahome_regions,
|
||||||
|
),
|
||||||
|
rememberLayoutFromResource(
|
||||||
|
resource = Res.drawable.icon_d100,
|
||||||
|
offset = IntOffset(x = 1740, y = 910),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.graphicsLayer { clip = true }
|
||||||
|
.onCameraControl(scope = scope, scene = scene)
|
||||||
|
.drawScene(scene = scene)
|
||||||
|
.fogOfWar(scene = scene)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(alignment = Alignment.BottomEnd)
|
||||||
|
.padding(end = campaign.rightPanel.value.width)
|
||||||
|
.padding(all = 8.dp)
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
scene.camera.handleZoom(
|
||||||
|
zoomIn = true,
|
||||||
|
power = 0.3f,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(Res.drawable.ic_zoom_in_map_24dp),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
scene.camera.handleZoom(
|
||||||
|
zoomIn = false,
|
||||||
|
power = 0.3f,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(Res.drawable.ic_zoom_out_map_24dp),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
scene.camera.resetPosition()
|
||||||
|
}
|
||||||
|
scope.launch {
|
||||||
|
scene.camera.resetZoom()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(Res.drawable.ic_center_focus_weak_24dp),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
scene.layouts.getOrNull(1)?.let {
|
||||||
|
it.alpha(alpha = if (it.alpha == 0f) 1f else 0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(Res.drawable.ic_visibility_24dp),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
fun rememberLayoutFromResource(
|
||||||
|
resource: DrawableResource,
|
||||||
|
offset: IntOffset = IntOffset.Zero,
|
||||||
|
): Layout {
|
||||||
|
val texture = imageResource(
|
||||||
|
resource = resource,
|
||||||
|
)
|
||||||
|
return remember(resource) {
|
||||||
|
Layout(
|
||||||
|
texture = texture,
|
||||||
|
offset = offset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
fun rememberScene(
|
||||||
|
camera: Camera,
|
||||||
|
fogOfWar: FogOfWar,
|
||||||
|
vararg layouts: Layout,
|
||||||
|
): Scene {
|
||||||
|
return remember {
|
||||||
|
Scene(
|
||||||
|
camera = camera,
|
||||||
|
layouts = layouts.toList(),
|
||||||
|
fogOfWar = fogOfWar,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Modifier.drawScene(
|
||||||
|
scene: Scene,
|
||||||
|
): Modifier = this.drawWithCache {
|
||||||
|
onDrawBehind {
|
||||||
|
scene.layouts.forEach { layout ->
|
||||||
|
drawImage(
|
||||||
|
image = layout.texture,
|
||||||
|
srcOffset = scene.camera.offset - layout.offset,
|
||||||
|
srcSize = scene.camera.cameraSizeZoomed,
|
||||||
|
dstSize = scene.camera.cameraSize,
|
||||||
|
alpha = layout.alpha,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Modifier.fogOfWar(
|
||||||
|
scene: Scene,
|
||||||
|
): Modifier = this.drawWithCache {
|
||||||
|
onDrawBehind {
|
||||||
|
drawRect(color = scene.fogOfWar.color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import androidx.compose.foundation.TooltipPlacement
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
|
|
@ -56,7 +56,7 @@ fun BasicTooltipLayout(
|
||||||
tips = tooltip,
|
tips = tooltip,
|
||||||
tooltip = {
|
tooltip = {
|
||||||
BasicTooltip(
|
BasicTooltip(
|
||||||
modifier = Modifier.width(width = 448.dp),
|
modifier = Modifier.widthIn(max = 448.dp),
|
||||||
elevation = elevation,
|
elevation = elevation,
|
||||||
tooltip = it,
|
tooltip = it,
|
||||||
)
|
)
|
||||||
|
|
@ -72,7 +72,9 @@ private fun BasicTooltip(
|
||||||
tooltip: BasicTooltipUio,
|
tooltip: BasicTooltipUio,
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.padding(16.dp).then(other = modifier),
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.then(other = modifier),
|
||||||
color = MaterialTheme.colors.surface,
|
color = MaterialTheme.colors.surface,
|
||||||
elevation = elevation,
|
elevation = elevation,
|
||||||
shape = remember { RoundedCornerShape(4.dp) }
|
shape = remember { RoundedCornerShape(4.dp) }
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ package com.pixelized.desktop.lwa.ui.screen.campaign
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
|
@ -20,6 +20,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.KeyEvent
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
import androidx.compose.ui.input.key.isAltPressed
|
import androidx.compose.ui.input.key.isAltPressed
|
||||||
import androidx.compose.ui.input.key.isCtrlPressed
|
import androidx.compose.ui.input.key.isCtrlPressed
|
||||||
|
|
@ -42,6 +43,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.character.diminished.CharacterSheetDiminishedDialogViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler
|
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.key.KeyHandler
|
||||||
|
import com.pixelized.desktop.lwa.ui.composable.scene.Scene
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
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.navigation.screen.destination.navigateToLevelScreen
|
||||||
import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlay
|
import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlay
|
||||||
|
|
@ -49,13 +51,14 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetai
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.DetailPanelUio
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.DetailPanelUio
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc.NpcRibbon
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbon
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbon
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.NpcRibbonViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel
|
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.CampaignChat
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbar
|
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.screen.campaign.toolbar.CampaignToolbarViewModel
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
|
|
@ -67,6 +70,7 @@ val LocalCampaignLayoutScope = compositionLocalOf<CampaignLayoutScope> {
|
||||||
fun CampaignScreen(
|
fun CampaignScreen(
|
||||||
playerRibbonViewModel: PlayerRibbonViewModel = koinViewModel(),
|
playerRibbonViewModel: PlayerRibbonViewModel = koinViewModel(),
|
||||||
playerDetailViewModel: CharacterDetailPanelViewModel = koinViewModel(key = "player"),
|
playerDetailViewModel: CharacterDetailPanelViewModel = koinViewModel(key = "player"),
|
||||||
|
npcRibbonViewModel: NpcRibbonViewModel = koinViewModel(),
|
||||||
npcDetailViewModel: CharacterDetailPanelViewModel = koinViewModel(key = "npc"),
|
npcDetailViewModel: CharacterDetailPanelViewModel = koinViewModel(key = "npc"),
|
||||||
characteristicDialogViewModel: CharacterSheetCharacteristicDialogViewModel = koinViewModel(),
|
characteristicDialogViewModel: CharacterSheetCharacteristicDialogViewModel = koinViewModel(),
|
||||||
dismissedViewModel: CharacterSheetDiminishedDialogViewModel = koinViewModel(),
|
dismissedViewModel: CharacterSheetDiminishedDialogViewModel = koinViewModel(),
|
||||||
|
|
@ -96,7 +100,9 @@ fun CampaignScreen(
|
||||||
|
|
||||||
},
|
},
|
||||||
main = {
|
main = {
|
||||||
|
Scene(
|
||||||
|
modifier = Modifier.matchParentSize(),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
chat = {
|
chat = {
|
||||||
CampaignChat(
|
CampaignChat(
|
||||||
|
|
@ -115,9 +121,10 @@ fun CampaignScreen(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
leftPanel = {
|
leftPanel = {
|
||||||
PlayerRibbon(
|
CharacterRibbon(
|
||||||
modifier = Modifier.fillMaxHeight(),
|
modifier = Modifier.fillMaxHeight(),
|
||||||
viewModel = playerRibbonViewModel,
|
viewModel = playerRibbonViewModel,
|
||||||
|
layoutDirection = LayoutDirection.Ltr,
|
||||||
onCharacterLeftClick = {
|
onCharacterLeftClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
playerDetailViewModel.showCharacter(
|
playerDetailViewModel.showCharacter(
|
||||||
|
|
@ -140,8 +147,10 @@ fun CampaignScreen(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
rightPanel = {
|
rightPanel = {
|
||||||
NpcRibbon(
|
CharacterRibbon(
|
||||||
modifier = Modifier.fillMaxHeight(),
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
viewModel = npcRibbonViewModel,
|
||||||
|
layoutDirection = LayoutDirection.Rtl,
|
||||||
onCharacterLeftClick = {
|
onCharacterLeftClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
npcDetailViewModel.showCharacter(
|
npcDetailViewModel.showCharacter(
|
||||||
|
|
@ -167,10 +176,11 @@ fun CampaignScreen(
|
||||||
CharacterDetailPanel(
|
CharacterDetailPanel(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(all = 8.dp)
|
.padding(all = 8.dp)
|
||||||
|
.padding(start = MaterialTheme.lwa.size.portrait.minimized.width + 8.dp)
|
||||||
.width(width = 128.dp * 4)
|
.width(width = 128.dp * 4)
|
||||||
.fillMaxHeight(),
|
.fillMaxHeight(),
|
||||||
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Ltr),
|
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Ltr),
|
||||||
detailPanelViewModel = npcDetailViewModel,
|
detailPanelViewModel = playerDetailViewModel,
|
||||||
characterDiminishedViewModel = dismissedViewModel,
|
characterDiminishedViewModel = dismissedViewModel,
|
||||||
characteristicDialogViewModel = characteristicDialogViewModel,
|
characteristicDialogViewModel = characteristicDialogViewModel,
|
||||||
alterationViewModel = alterationViewModel,
|
alterationViewModel = alterationViewModel,
|
||||||
|
|
@ -183,10 +193,11 @@ fun CampaignScreen(
|
||||||
CharacterDetailPanel(
|
CharacterDetailPanel(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(all = 8.dp)
|
.padding(all = 8.dp)
|
||||||
|
.padding(end = MaterialTheme.lwa.size.portrait.minimized.width + 8.dp)
|
||||||
.width(width = 128.dp * 4)
|
.width(width = 128.dp * 4)
|
||||||
.fillMaxHeight(),
|
.fillMaxHeight(),
|
||||||
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Rtl),
|
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Rtl),
|
||||||
detailPanelViewModel = playerDetailViewModel,
|
detailPanelViewModel = npcDetailViewModel,
|
||||||
characterDiminishedViewModel = dismissedViewModel,
|
characterDiminishedViewModel = dismissedViewModel,
|
||||||
characteristicDialogViewModel = characteristicDialogViewModel,
|
characteristicDialogViewModel = characteristicDialogViewModel,
|
||||||
alterationViewModel = alterationViewModel,
|
alterationViewModel = alterationViewModel,
|
||||||
|
|
@ -205,7 +216,7 @@ fun CampaignScreen(
|
||||||
characteristicDialogViewModel.changeSubCharacteristic(
|
characteristicDialogViewModel.changeSubCharacteristic(
|
||||||
characterSheetId = dialog.characterSheetId,
|
characterSheetId = dialog.characterSheetId,
|
||||||
characteristic = dialog.characteristic,
|
characteristic = dialog.characteristic,
|
||||||
useArmor= dialog.enableArmor?.checked?.value == true,
|
useArmor = dialog.enableArmor?.checked?.value == true,
|
||||||
value = dialog.value.valueFlow.value.toIntOrNull() ?: 0,
|
value = dialog.value.valueFlow.value.toIntOrNull() ?: 0,
|
||||||
)
|
)
|
||||||
characteristicDialogViewModel.hideSubCharacteristicDialog()
|
characteristicDialogViewModel.hideSubCharacteristicDialog()
|
||||||
|
|
@ -307,6 +318,7 @@ private fun CampaignLayout(
|
||||||
) {
|
) {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
|
val mainState = remember { mutableStateOf(DpSize.Unspecified) }
|
||||||
val leftOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
|
val leftOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
|
||||||
val leftPanelState = remember { mutableStateOf(DpSize.Unspecified) }
|
val leftPanelState = remember { mutableStateOf(DpSize.Unspecified) }
|
||||||
val rightOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
|
val rightOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
|
||||||
|
|
@ -314,6 +326,7 @@ private fun CampaignLayout(
|
||||||
val chatOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
|
val chatOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
|
||||||
val scope = remember {
|
val scope = remember {
|
||||||
CampaignLayoutScope(
|
CampaignLayoutScope(
|
||||||
|
main = mainState,
|
||||||
leftOverlay = leftOverlayState,
|
leftOverlay = leftOverlayState,
|
||||||
leftPanel = leftPanelState,
|
leftPanel = leftPanelState,
|
||||||
rightOverlay = rightOverlayState,
|
rightOverlay = rightOverlayState,
|
||||||
|
|
@ -334,30 +347,35 @@ private fun CampaignLayout(
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(alignment = Alignment.Center)
|
.onSizeChanged { mainState.value = it.toDp(density) }
|
||||||
.fillMaxSize(),
|
.matchParentSize(),
|
||||||
) {
|
) {
|
||||||
main()
|
main()
|
||||||
}
|
}
|
||||||
Row {
|
Box(
|
||||||
Box(
|
modifier = Modifier
|
||||||
modifier = Modifier.onSizeChanged { leftPanelState.value = it.toDp(density) },
|
.align(alignment = Alignment.BottomStart)
|
||||||
) {
|
.padding(
|
||||||
leftPanel()
|
start = MaterialTheme.lwa.size.portrait.minimized.width * 2 + 20.dp,
|
||||||
}
|
end = MaterialTheme.lwa.size.portrait.minimized.width * 2 + 20.dp + 56.dp,
|
||||||
Box(
|
)
|
||||||
modifier = Modifier
|
.onSizeChanged { chatOverlayState.value = it.toDp(density) },
|
||||||
.align(alignment = Alignment.Bottom)
|
) {
|
||||||
.weight(weight = 1f)
|
chat()
|
||||||
.onSizeChanged { chatOverlayState.value = it.toDp(density) },
|
}
|
||||||
) {
|
Box(
|
||||||
chat()
|
modifier = Modifier
|
||||||
}
|
.align(alignment = Alignment.CenterStart)
|
||||||
Box(
|
.onSizeChanged { leftPanelState.value = it.toDp(density) },
|
||||||
modifier = Modifier.onSizeChanged { rightPanelState.value = it.toDp(density) },
|
) {
|
||||||
) {
|
leftPanel()
|
||||||
rightPanel()
|
}
|
||||||
}
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(alignment = Alignment.CenterEnd)
|
||||||
|
.onSizeChanged { rightPanelState.value = it.toDp(density) },
|
||||||
|
) {
|
||||||
|
rightPanel()
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -392,24 +410,31 @@ private fun CampaignKeyHandler(
|
||||||
onPlayerNumber: (index: Int) -> Unit,
|
onPlayerNumber: (index: Int) -> Unit,
|
||||||
onAltPLayerNumber: (index: Int) -> Unit,
|
onAltPLayerNumber: (index: Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
fun KeyEvent.callback(index: Int) {
|
||||||
|
if (isAltPressed) onAltPLayerNumber(index) else onPlayerNumber(index)
|
||||||
|
}
|
||||||
KeyHandler {
|
KeyHandler {
|
||||||
if (it.type != KeyEventType.KeyDown) return@KeyHandler false
|
if (it.type != KeyEventType.KeyDown) {
|
||||||
|
return@KeyHandler false
|
||||||
|
}
|
||||||
if (it.key == Key.Escape) {
|
if (it.key == Key.Escape) {
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
return@KeyHandler true
|
return@KeyHandler true
|
||||||
}
|
}
|
||||||
if (it.isCtrlPressed.not()) return@KeyHandler false
|
if (it.isCtrlPressed.not()) {
|
||||||
|
return@KeyHandler false
|
||||||
|
}
|
||||||
when (it.key) {
|
when (it.key) {
|
||||||
Key.Escape -> onDismissRequest()
|
Key.Escape -> onDismissRequest()
|
||||||
Key.One, Key.NumPad1 -> if (it.isAltPressed) onAltPLayerNumber(0) else onPlayerNumber(0)
|
Key.One, Key.NumPad1 -> it.callback(index = 0)
|
||||||
Key.Two, Key.NumPad2 -> if (it.isAltPressed) onAltPLayerNumber(1) else onPlayerNumber(1)
|
Key.Two, Key.NumPad2 -> it.callback(index = 1)
|
||||||
Key.Three, Key.NumPad3 -> if (it.isAltPressed) onAltPLayerNumber(2) else onPlayerNumber(2)
|
Key.Three, Key.NumPad3 -> it.callback(index = 2)
|
||||||
Key.Four, Key.NumPad4 -> if (it.isAltPressed) onAltPLayerNumber(3) else onPlayerNumber(3)
|
Key.Four, Key.NumPad4 -> it.callback(index = 3)
|
||||||
Key.Five, Key.NumPad5 -> if (it.isAltPressed) onAltPLayerNumber(4) else onPlayerNumber(4)
|
Key.Five, Key.NumPad5 -> it.callback(index = 4)
|
||||||
Key.Six, Key.NumPad6 -> if (it.isAltPressed) onAltPLayerNumber(5) else onPlayerNumber(5)
|
Key.Six, Key.NumPad6 -> it.callback(index = 5)
|
||||||
Key.Seven, Key.NumPad7 -> if (it.isAltPressed) onAltPLayerNumber(6) else onPlayerNumber(6)
|
Key.Seven, Key.NumPad7 -> it.callback(index = 6)
|
||||||
Key.Eight, Key.NumPad8 -> if (it.isAltPressed) onAltPLayerNumber(7) else onPlayerNumber(7)
|
Key.Eight, Key.NumPad8 -> it.callback(index = 7)
|
||||||
Key.Nine, Key.NumPad9 -> if (it.isAltPressed) onAltPLayerNumber(8) else onPlayerNumber(8)
|
Key.Nine, Key.NumPad9 -> it.callback(index = 8)
|
||||||
else -> return@KeyHandler false
|
else -> return@KeyHandler false
|
||||||
}
|
}
|
||||||
return@KeyHandler true
|
return@KeyHandler true
|
||||||
|
|
@ -425,6 +450,7 @@ private fun IntSize.toDp(density: Density) = with(density) {
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class CampaignLayoutScope(
|
data class CampaignLayoutScope(
|
||||||
|
val main: State<DpSize>,
|
||||||
val leftOverlay: State<DpSize>,
|
val leftOverlay: State<DpSize>,
|
||||||
val leftPanel: State<DpSize>,
|
val leftPanel: State<DpSize>,
|
||||||
val rightOverlay: State<DpSize>,
|
val rightOverlay: State<DpSize>,
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,12 @@ import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortrait
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortrait
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRoll
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRoll
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlteration
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlteration
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel
|
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CharacterRibbon(
|
fun CharacterRibbon(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
layoutDirection: LayoutDirection,
|
layoutDirection: LayoutDirection,
|
||||||
viewModel: PlayerRibbonViewModel = koinViewModel(),
|
viewModel: CharacterRibbonViewModel,
|
||||||
padding: PaddingValues = PaddingValues(all = 8.dp),
|
padding: PaddingValues = PaddingValues(all = 8.dp),
|
||||||
onCharacterLeftClick: (characterSheetId: String) -> Unit,
|
onCharacterLeftClick: (characterSheetId: String) -> Unit,
|
||||||
onCharacterRightClick: (characterSheetId: String) -> Unit,
|
onCharacterRightClick: (characterSheetId: String) -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc
|
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
|
||||||
|
|
||||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||||
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||||
|
|
@ -6,8 +6,6 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetReposit
|
||||||
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
|
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
|
||||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||||
import com.pixelized.desktop.lwa.repository.settings.model.Settings
|
import com.pixelized.desktop.lwa.repository.settings.model.Settings
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonViewModel
|
|
||||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||||
|
|
||||||
class NpcRibbonViewModel(
|
class NpcRibbonViewModel(
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player
|
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
|
||||||
|
|
||||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||||
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||||
|
|
@ -6,8 +6,6 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetReposit
|
||||||
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
|
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
|
||||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||||
import com.pixelized.desktop.lwa.repository.settings.model.Settings
|
import com.pixelized.desktop.lwa.repository.settings.model.Settings
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonViewModel
|
|
||||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||||
|
|
||||||
class PlayerRibbonViewModel(
|
class PlayerRibbonViewModel(
|
||||||
|
|
@ -32,6 +32,7 @@ import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class CharacterRibbonAlterationUio(
|
data class CharacterRibbonAlterationUio(
|
||||||
|
|
||||||
val icon: String,
|
val icon: String,
|
||||||
val tooltips: BasicTooltipUio?,
|
val tooltips: BasicTooltipUio?,
|
||||||
)
|
)
|
||||||
|
|
@ -44,60 +45,56 @@ fun CharacterRibbonAlteration(
|
||||||
direction: LayoutDirection,
|
direction: LayoutDirection,
|
||||||
status: List<List<CharacterRibbonAlterationUio>>,
|
status: List<List<CharacterRibbonAlterationUio>>,
|
||||||
) {
|
) {
|
||||||
val currentDirection: LayoutDirection = LocalLayoutDirection.current
|
val currentDirection = LocalLayoutDirection.current
|
||||||
|
|
||||||
CompositionLocalProvider(
|
Row(
|
||||||
LocalLayoutDirection provides direction
|
modifier = Modifier
|
||||||
|
.animateContentSize()
|
||||||
|
.size(size = size)
|
||||||
|
.then(other = modifier),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||||
) {
|
) {
|
||||||
Row(
|
status.forEach { columns ->
|
||||||
modifier = Modifier
|
Column(
|
||||||
.animateContentSize()
|
modifier = Modifier.animateContentSize(),
|
||||||
.size(size = size)
|
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
|
||||||
.then(other = modifier),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
|
||||||
) {
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalLayoutDirection provides currentDirection
|
|
||||||
) {
|
) {
|
||||||
status.forEach { columns ->
|
CompositionLocalProvider(
|
||||||
Column(
|
LocalLayoutDirection provides LayoutDirection.Ltr
|
||||||
modifier = Modifier.animateContentSize(),
|
) {
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
|
columns.forEach {
|
||||||
) {
|
BasicTooltipLayout(
|
||||||
columns.forEach {
|
delayMillis = 0,
|
||||||
BasicTooltipLayout(
|
tooltip = it.tooltips,
|
||||||
delayMillis = 0,
|
tooltipPlacement = remember(currentDirection) {
|
||||||
tooltip = it.tooltips,
|
TooltipPlacement.ComponentRect(
|
||||||
tooltipPlacement = remember(currentDirection) {
|
anchor = when (direction) {
|
||||||
TooltipPlacement.ComponentRect(
|
LayoutDirection.Ltr -> Alignment.CenterEnd
|
||||||
anchor = when (direction) {
|
LayoutDirection.Rtl -> Alignment.CenterStart
|
||||||
LayoutDirection.Ltr -> Alignment.TopStart
|
},
|
||||||
LayoutDirection.Rtl -> Alignment.TopEnd
|
alignment = when (direction) {
|
||||||
},
|
LayoutDirection.Ltr -> Alignment.CenterEnd
|
||||||
alignment = when (direction) {
|
LayoutDirection.Rtl -> Alignment.CenterStart
|
||||||
LayoutDirection.Ltr -> Alignment.BottomEnd
|
},
|
||||||
LayoutDirection.Rtl -> Alignment.BottomStart
|
)
|
||||||
},
|
},
|
||||||
|
content = {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = it.icon,
|
||||||
|
transitionSpec = { fadeIn() togetherWith fadeOut() },
|
||||||
|
) { icon ->
|
||||||
|
AsyncImage(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
model = ImageRequest.Builder(context = PlatformContext.INSTANCE)
|
||||||
|
.data(data = icon)
|
||||||
|
.size(size = 48)
|
||||||
|
.build(),
|
||||||
|
filterQuality = FilterQuality.High,
|
||||||
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
},
|
|
||||||
content = {
|
|
||||||
AnimatedContent(
|
|
||||||
targetState = it.icon,
|
|
||||||
transitionSpec = { fadeIn() togetherWith fadeOut() },
|
|
||||||
) { icon ->
|
|
||||||
AsyncImage(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
model = ImageRequest.Builder(context = PlatformContext.INSTANCE)
|
|
||||||
.data(data = icon)
|
|
||||||
.size(size = 48)
|
|
||||||
.build(),
|
|
||||||
filterQuality = FilterQuality.High,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortrait
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRoll
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlteration
|
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun NpcRibbon(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
viewModel: NpcRibbonViewModel = koinViewModel(),
|
|
||||||
padding: PaddingValues = PaddingValues(all = 8.dp),
|
|
||||||
onCharacterLeftClick: (characterSheetId: String) -> Unit,
|
|
||||||
onCharacterRightClick: (characterSheetId: String) -> Unit,
|
|
||||||
onLevelUp: (characterSheetId: String) -> Unit,
|
|
||||||
) {
|
|
||||||
val characters = viewModel.characters.collectAsState()
|
|
||||||
|
|
||||||
LazyColumn(
|
|
||||||
modifier = modifier,
|
|
||||||
contentPadding = padding,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
|
||||||
) {
|
|
||||||
items(
|
|
||||||
items = characters.value,
|
|
||||||
key = { it.characterSheetId },
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.animateItem()
|
|
||||||
.graphicsLayer { if (it.hideOverruled) this.alpha = 0.3f },
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
|
||||||
) {
|
|
||||||
CharacterRibbonAlteration(
|
|
||||||
status = it.status,
|
|
||||||
direction = LayoutDirection.Rtl,
|
|
||||||
)
|
|
||||||
Box {
|
|
||||||
CharacterRibbonPortrait(
|
|
||||||
character = it.portrait,
|
|
||||||
onCharacterLeftClick = { onCharacterLeftClick(it.characterSheetId) },
|
|
||||||
onCharacterRightClick = { onCharacterRightClick(it.characterSheetId) },
|
|
||||||
onLevelUp = { onLevelUp(it.characterSheetId) },
|
|
||||||
)
|
|
||||||
CharacterRibbonRoll(
|
|
||||||
value = viewModel.roll(characterSheetId = it.characterSheetId).value,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortrait
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRoll
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlteration
|
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PlayerRibbon(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
viewModel: PlayerRibbonViewModel = koinViewModel(),
|
|
||||||
padding: PaddingValues = PaddingValues(all = 8.dp),
|
|
||||||
onCharacterLeftClick: (characterSheetId: String) -> Unit,
|
|
||||||
onCharacterRightClick: (characterSheetId: String) -> Unit,
|
|
||||||
onLevelUp: (characterSheetId: String) -> Unit,
|
|
||||||
) {
|
|
||||||
val characters = viewModel.characters.collectAsState()
|
|
||||||
|
|
||||||
LazyColumn(
|
|
||||||
modifier = modifier,
|
|
||||||
contentPadding = padding,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
|
||||||
) {
|
|
||||||
items(
|
|
||||||
items = characters.value,
|
|
||||||
key = { it.characterSheetId },
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.animateItem()
|
|
||||||
.graphicsLayer { if (it.hideOverruled) this.alpha = 0.3f },
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
|
||||||
) {
|
|
||||||
Box {
|
|
||||||
CharacterRibbonPortrait(
|
|
||||||
character = it.portrait,
|
|
||||||
onCharacterLeftClick = { onCharacterLeftClick(it.characterSheetId) },
|
|
||||||
onCharacterRightClick = { onCharacterRightClick(it.characterSheetId) },
|
|
||||||
onLevelUp = { onLevelUp(it.characterSheetId) },
|
|
||||||
)
|
|
||||||
CharacterRibbonRoll(
|
|
||||||
value = viewModel.roll(characterSheetId = it.characterSheetId).value,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
CharacterRibbonAlteration(
|
|
||||||
status = it.status,
|
|
||||||
direction = LayoutDirection.Ltr,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,6 +10,7 @@ koin = "4.0.0"
|
||||||
turtle = "0.10.0"
|
turtle = "0.10.0"
|
||||||
logback = "1.5.17"
|
logback = "1.5.17"
|
||||||
coil = "3.1.0"
|
coil = "3.1.0"
|
||||||
|
zoomable = "2.7.0"
|
||||||
ui-graphics-android = "1.7.8"
|
ui-graphics-android = "1.7.8"
|
||||||
buildkonfig = "0.17.0"
|
buildkonfig = "0.17.0"
|
||||||
|
|
||||||
|
|
@ -35,6 +36,7 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-
|
||||||
# UI.
|
# UI.
|
||||||
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
|
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
|
||||||
coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
|
coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
|
||||||
|
engawapg-zoomable = { module = "net.engawapg.lib:zoomable", version.ref = "zoomable" }
|
||||||
# Injection with Koin
|
# Injection with Koin
|
||||||
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||||
koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" }
|
koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue