Some small fix related to the network UI and some Shader preparation.

This commit is contained in:
Thomas Andres Gomez 2025-03-12 22:23:20 +01:00
parent 16b2b49f03
commit a93bb9d3f5
18 changed files with 196 additions and 76 deletions

View file

@ -33,6 +33,8 @@ kotlin {
// composable component.
implementation(libs.coil.compose)
implementation(libs.coil.network.ktor)
// implementation("com.mikepenz.hypnoticcanvas:hypnoticcanvas:0.3.0")
// implementation("com.mikepenz.hypnoticcanvas:hypnoticcanvas-shaders:0.3.0")
// network
implementation(libs.kotlinx.serialization.json)
implementation(libs.ktor.serialization.json)

View file

@ -78,7 +78,10 @@ fun BlurContent(
Box(
modifier = Modifier
.matchParentSize()
.blur(radius = animatedBlur.value, edgeTreatment = BlurredEdgeTreatment.Unbounded)
.blur(
radius = animatedBlur.value,
edgeTreatment = BlurredEdgeTreatment.Unbounded,
)
.drawWithContent {
drawContent()
drawRect(color = animatedBackground.value)

View file

@ -2,6 +2,7 @@ package com.pixelized.desktop.lwa.ui.composable.shapes
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.MaterialTheme
@ -9,38 +10,48 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.desktop.lwa.ui.theme.shapes.ArrowShape
@Composable
fun ArrowShape() {
val colorScheme = MaterialTheme.lwa.colorScheme
val arrow = remember {
ArrowShape(
core = 4.dp,
head = 4.dp,
)
fun ArrowShape(
modifier: Modifier = Modifier,
size: Dp = 24.dp,
core: Dp = 4.dp,
head: Dp = 4.dp,
thickness: Dp = 1.dp,
paddingValues: PaddingValues = PaddingValues(horizontal = 2.dp, vertical = 3.dp),
color: Color = MaterialTheme.lwa.colorScheme.base.primary,
) {
val arrow = remember(core, head) {
ArrowShape(core = core, head = head)
}
Box(
modifier = Modifier
.size(size = 24.dp)
.padding(horizontal = 2.dp)
.padding(top = 3.dp, bottom = 3.dp)
.border(
width = 1.dp,
color = colorScheme.portrait.levelUp,
shape = arrow,
.size(size = size)
.padding(
paddingValues = paddingValues,
)
.shadow(
elevation = 1.dp,
.border(
width = thickness,
color = color,
shape = arrow,
)
.shadow(
elevation = 2.dp,
shape = arrow,
ambientColor = colorScheme.portrait.levelUp,
spotColor = colorScheme.portrait.levelUp,
ambientColor = MaterialTheme.lwa.colorScheme.portrait.levelUp,
spotColor = MaterialTheme.lwa.colorScheme.portrait.levelUp,
)
.shadow(
elevation = 1.dp,
shape = arrow,
)
.then(
other = modifier,
)
)
}

View file

@ -153,6 +153,7 @@ fun RollPage(
.graphicsLayer {
this.alpha = 0.8f
this.rotationZ = viewModel.rollRotation.value
},
tint = MaterialTheme.colors.primary,
painter = painterResource(Res.drawable.ic_d20_24dp),

View file

@ -29,7 +29,6 @@ import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.LocalRollHostState
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel
import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeader
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheet
@ -120,7 +119,6 @@ fun CharacterDetailPanel(
}
}
)
}
@Composable

View file

@ -90,7 +90,9 @@ fun CharacterDetailHeader(
Column(
modifier = modifier,
) {
Row(modifier = Modifier.padding(start = 16.dp)) {
Row(
modifier = Modifier.padding(start = 16.dp),
) {
Row(
modifier = Modifier
.weight(1f)

View file

@ -1,6 +1,7 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.EaseOutCirc
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
@ -62,17 +63,10 @@ data class PlayerPortraitUio(
val levelUp: Boolean,
)
object PlayerPortrait {
object Default {
val bloodColor = Color(0xbb0a1e).copy(alpha = 0.25f)
}
}
@Composable
fun PlayerPortrait(
modifier: Modifier = Modifier,
size: DpSize,
bloodColor: Color = PlayerPortrait.Default.bloodColor,
character: PlayerPortraitUio,
onCharacter: (id: Campaign.CharacterInstance.Id) -> Unit,
onLevelUp: (id: Campaign.CharacterInstance.Id) -> Unit,
@ -101,17 +95,22 @@ fun PlayerPortrait(
}
BloodOverlay(
bloodColor = bloodColor,
maxHp = character.maxHp.toFloat(),
hp = character.hp.toFloat(),
)
if (character.levelUp) {
AnimatedVisibility(
modifier = Modifier.offset(x = (-8).dp, y = (-8).dp),
visible = character.levelUp,
enter = fadeIn(),
exit = fadeOut(),
) {
IconButton(
modifier = Modifier.offset(x = (-8).dp, y = (-8).dp),
onClick = { onLevelUp(character.id) },
) {
ArrowShape()
ArrowShape(
color = MaterialTheme.lwa.colorScheme.portrait.levelUp,
)
}
}
@ -176,9 +175,9 @@ fun PlayerPortrait(
@Composable
private fun BloodOverlay(
modifier: Modifier = Modifier,
bloodColor: Color = MaterialTheme.lwa.colorScheme.portrait.blood,
maxHp: Float,
hp: Float,
bloodColor: Color = PlayerPortrait.Default.bloodColor,
) {
val animatedRatio = animateFloatAsState(
targetValue = min(maxHp, max(0f, (maxHp - hp) / maxHp)),

View file

@ -64,10 +64,10 @@ import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.LevelUpDestination
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult
import com.pixelized.desktop.lwa.ui.screen.levelup.skill.LevelUpCharacteristic
import com.pixelized.desktop.lwa.ui.screen.levelup.skill.LevelUpCharacteristicUio
import com.pixelized.desktop.lwa.ui.screen.levelup.skill.LevelUpSkill
import com.pixelized.desktop.lwa.ui.screen.levelup.skill.LevelUpSkillUio
import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpCharacteristic
import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpCharacteristicUio
import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSkill
import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSkillUio
import com.pixelized.desktop.lwa.ui.theme.lwa
import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
@ -224,7 +224,9 @@ private fun LevelUpContent(
Text(
style = MaterialTheme.lwa.typography.base.body1,
text = (header.value?.level ?: 0).let {
stringResource(Res.string.level_up__character_level_description, it, it + 1)
stringResource(
Res.string.level_up__character_level_description, it, it + 1
)
},
)
}

View file

@ -3,8 +3,8 @@ package com.pixelized.desktop.lwa.ui.screen.levelup
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio
import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction.RollActionUio
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult
import com.pixelized.desktop.lwa.ui.screen.levelup.skill.LevelUpCharacteristicUio
import com.pixelized.desktop.lwa.ui.screen.levelup.skill.LevelUpSkillUio
import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpCharacteristicUio
import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSkillUio
import com.pixelized.shared.lwa.model.AlteredCharacterSheet
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
import com.pixelized.shared.lwa.model.campaign.Campaign

View file

@ -1,4 +1,4 @@
package com.pixelized.desktop.lwa.ui.screen.levelup.skill
package com.pixelized.desktop.lwa.ui.screen.levelup.items
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.SizeTransform
@ -10,7 +10,6 @@ import androidx.compose.animation.togetherWith
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
@ -66,8 +65,8 @@ fun LevelUpCharacteristic(
.align(alignment = Alignment.Center),
targetState = characteristic.selected,
transitionSpec = {
val enter = fadeIn() + slideInVertically { -32 }
val exit = fadeOut() + slideOutVertically { 32 }
val enter = fadeIn() + slideInVertically { -16 }
val exit = fadeOut() + slideOutVertically { 16 }
enter togetherWith exit using SizeTransform(clip = false)
},
) {

View file

@ -1,4 +1,4 @@
package com.pixelized.desktop.lwa.ui.screen.levelup.skill
package com.pixelized.desktop.lwa.ui.screen.levelup.items
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.SizeTransform

View file

@ -2,8 +2,11 @@ package com.pixelized.desktop.lwa.ui.screen.network
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Arrangement
@ -186,50 +189,57 @@ fun NetworkPage(
) {
val snack = LocalSnackHost.current
Surface(
Box(
modifier = modifier,
) {
BlurContent(
modifier = Modifier.fillMaxSize(),
controller = viewModel.blurController,
) {
NetworkContent(
modifier = Modifier.fillMaxSize(),
network = viewModel.network.collectAsState(),
onPlayerChange = viewModel::onPlayerNameChange,
onHostChange = viewModel::onHostChange,
onResetHostChange = viewModel::onResetHostChange,
onPortChange = viewModel::onPortChange,
onResetPortChange = viewModel::onResetPortChange,
onConnect = viewModel::connect,
onDisconnect = viewModel::disconnect,
)
Surface {
NetworkContent(
modifier = Modifier.fillMaxSize(),
network = viewModel.network.collectAsState(),
onPlayerChange = viewModel::onPlayerNameChange,
onHostChange = viewModel::onHostChange,
onResetHostChange = viewModel::onResetHostChange,
onPortChange = viewModel::onPortChange,
onResetPortChange = viewModel::onResetPortChange,
onConnect = viewModel::connect,
onDisconnect = viewModel::disconnect,
)
}
}
AnimatedContent(
modifier = Modifier.size(size = 64.dp),
modifier = Modifier
.align(alignment = Alignment.Center)
.size(size = 32.dp),
targetState = viewModel.isLoading.value,
transitionSpec = { fadeIn() togetherWith fadeOut() },
transitionSpec = {
val enter = fadeIn() + slideInVertically { 64 }
val exit = fadeOut() + slideOutVertically { 64 }
enter togetherWith exit using SizeTransform(clip = false)
},
) {
when (it) {
true -> CircularProgressIndicator()
else -> Box(modifier = Modifier)
}
}
LaunchedEffect(Unit) {
viewModel.message.collect {
snack.showSnackbar(
message = it,
duration = SnackbarDuration.Short,
)
}
}
ErrorSnack(
error = viewModel.networkError,
)
}
LaunchedEffect(Unit) {
viewModel.message.collect {
snack.showSnackbar(
message = it,
duration = SnackbarDuration.Short,
)
}
}
ErrorSnack(
error = viewModel.networkError,
)
}
@Composable

View file

@ -9,6 +9,7 @@ import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow

View file

@ -0,0 +1,67 @@
//package com.pixelized.desktop.lwa.ui.shaders
//
//import androidx.compose.animation.core.withInfiniteAnimationFrameMillis
//import androidx.compose.runtime.Composable
//import androidx.compose.runtime.getValue
//import androidx.compose.runtime.mutableStateOf
//import androidx.compose.runtime.produceState
//import androidx.compose.runtime.remember
//import androidx.compose.runtime.setValue
//import androidx.compose.ui.Modifier
//import androidx.compose.ui.draw.drawBehind
//import androidx.compose.ui.draw.drawWithContent
//import androidx.compose.ui.geometry.Size
//import androidx.compose.ui.graphics.BlendMode
//import androidx.compose.ui.graphics.Brush
//import androidx.compose.ui.graphics.Color
//import androidx.compose.ui.layout.onGloballyPositioned
//import com.mikepenz.hypnoticcanvas.NonAndroidRuntimeEffect
//import com.mikepenz.hypnoticcanvas.shaders.Shader
//import com.mikepenz.hypnoticcanvas.utils.round
//
//@Composable
//fun Modifier.shaderContent(
// shader: Shader,
// speed: Float = 1f,
// fallback: () -> Brush = {
// Brush.horizontalGradient(listOf(Color.Transparent, Color.Transparent))
// },
//): Modifier {
// val runtimeEffect = remember(shader) { NonAndroidRuntimeEffect(shader) }
// var size: Size by remember { mutableStateOf(Size(-1f, -1f)) }
// val speedModifier = shader.speedModifier
//
// val time by if (runtimeEffect.supported) {
// var startMillis = remember(shader) { -1L }
// produceState(0f, speedModifier) {
// while (true) {
// withInfiniteAnimationFrameMillis {
// if (startMillis < 0) startMillis = it
// value = ((it - startMillis) / 16.6f) / 10f
// }
// }
// }
// } else {
// mutableStateOf(-1f)
// }
//
// return this then Modifier
// .onGloballyPositioned {
// size = Size(it.size.width.toFloat(), it.size.height.toFloat())
// }
// .drawWithContent {
// drawContent()
// // set uniforms for the shaders
// runtimeEffect.update(
// shader = shader,
// time = (time * speed * speedModifier).round(3),
// width = size.width,
// height = size.height
// )
// if (runtimeEffect.ready) {
// drawRect(brush = runtimeEffect.build(), blendMode = BlendMode.SrcAtop)
// } else {
// drawRect(brush = fallback())
// }
// }
//}

View file

@ -0,0 +1,19 @@
//package com.pixelized.desktop.lwa.ui.shaders
//
//import androidx.compose.animation.core.withInfiniteAnimationFrameMillis
//import androidx.compose.runtime.Composable
//import androidx.compose.runtime.Stable
//import androidx.compose.runtime.State
//import androidx.compose.runtime.produceState
//
//@Composable
//@Stable
//fun rememberShaderTimeState(speed: Float = 1f): State<Float> {
// return produceState(0f) {
// while (true) {
// withInfiniteAnimationFrameMillis {
// value = it / 1000f * speed
// }
// }
// }
//}

View file

@ -4,6 +4,8 @@ import androidx.compose.ui.graphics.Color
object LwaColorPalette {
val DefaultScrimColor = Color.Black.copy(alpha = 0.4f)
val Blood = Color(0xFFBB0A1E)
val Gold = Color(0xFFffe900)
val Orange400 = Color(0xFFFFA726)
val Red400 = Color(0xFFFF7043)

View file

@ -31,6 +31,7 @@ data class LwaColors(
@Stable
data class Portrait(
val levelUp: Color,
val blood: Color,
)
@Stable
@ -81,7 +82,8 @@ fun darkLwaColorTheme(
)
),
portrait: LwaColors.Portrait = LwaColors.Portrait(
levelUp = Color(0xFFffe900),
levelUp = LwaColorPalette.Gold,
blood = LwaColorPalette.Blood.copy(alpha = .25f),
),
chat: LwaColors.Chat = LwaColors.Chat(
timestamp = base.secondary,

View file

@ -10,6 +10,7 @@ koin = "4.0.0"
turtle = "0.10.0"
logback = "1.5.17"
coil = "3.1.0"
ui-graphics-android = "1.7.8"
[plugins]
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
@ -50,4 +51,5 @@ ktor-server-negotiation = { group = 'io.ktor', name = 'ktor-server-content-negot
# Utilitary run command line.
turtle = { group = "com.lordcodes.turtle", name = "turtle", version.ref = "turtle" }
# Utilitary logging use by Ktor.
logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
androidx-ui-graphics-android = { group = "androidx.compose.ui", name = "ui-graphics-android", version.ref = "ui-graphics-android" }