Add action to the character sheet detail panel.

This commit is contained in:
Thomas Andres Gomez 2025-02-27 15:36:07 +01:00
parent b6d02c21be
commit 1fe75062b7
14 changed files with 469 additions and 56 deletions

View file

@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.shareIn
import kotlinx.serialization.json.Json
class RollHistoryRepository(
private val network: NetworkRepository,
@ -21,23 +20,4 @@ class RollHistoryRepository(
scope = scope,
started = SharingStarted.Eagerly,
)
suspend fun share(
characterId: String,
skillLabel: String,
rollDifficulty: String?,
rollValue: Int,
resultLabel: String?,
rollSuccessLimit: Int?,
) {
val content = RollMessage(
characterId = characterId,
skillLabel = skillLabel,
rollDifficulty = rollDifficulty,
rollValue = rollValue,
resultLabel = resultLabel,
rollSuccessLimit = rollSuccessLimit,
)
network.share(payload = content)
}
}

View file

@ -0,0 +1,69 @@
package com.pixelized.desktop.lwa.ui.composable.circle
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
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.utils.extention.dashedBorder
@Composable
fun MasteryShape(
modifier: Modifier = Modifier,
shape: Shape = CircleShape,
color: Color = MaterialTheme.lwa.colorScheme.base.onSurface,
borderWidth: Dp = 1.dp,
size: Dp = 12.dp,
multiplier: Int,
) {
when (multiplier) {
0 -> Box(
modifier = modifier
.size(size = size)
.dashedBorder(
width = borderWidth,
color = color,
shape = shape,
on = 3.dp,
off = 2.dp
)
)
1 -> Box(
modifier = modifier
.size(size = size)
.background(
color = color,
shape = shape,
)
)
else -> Box(
modifier = modifier
.size(size = size)
.border(
width = borderWidth,
color = color,
shape = shape
)
.padding(all = 2.dp)
) {
MasteryShape(
shape = shape,
color = color,
borderWidth = borderWidth,
size = size - borderWidth * 2,
multiplier = multiplier - 1,
)
}
}
}

View file

@ -30,6 +30,7 @@ import com.pixelized.desktop.lwa.ui.composable.character.characteristic.Characte
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
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetActionUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetCharacteristicUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetSkillUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetUio
@ -108,6 +109,11 @@ fun CharacterDetailPanel(
)
}
},
onAction = {
rollViewModel.prepareRoll(roll = it.roll)
rollViewModel.showOverlay()
blurController.show()
}
)
}
@ -122,6 +128,7 @@ fun CharacterDetailAnimatedPanel(
onCharacteristic: (CharacterDetailSheetCharacteristicUio) -> Unit,
onSkill: (CharacterDetailSheetSkillUio) -> Unit,
onUseSkill: (CharacterDetailSheetSkillUio) -> Unit,
onAction: (CharacterDetailSheetActionUio) -> Unit,
) {
Box(
modifier = modifier,
@ -159,6 +166,7 @@ fun CharacterDetailAnimatedPanel(
onCharacteristic = onCharacteristic,
onSkill = onSkill,
onUseSkill = onUseSkill,
onAction = onAction,
)
}
}
@ -179,6 +187,7 @@ fun CharacterDetailContent(
onCharacteristic: (CharacterDetailSheetCharacteristicUio) -> Unit,
onSkill: (CharacterDetailSheetSkillUio) -> Unit,
onUseSkill: (CharacterDetailSheetSkillUio) -> Unit,
onAction: (CharacterDetailSheetActionUio) -> Unit,
) {
Surface(
modifier = modifier.fillMaxSize(),
@ -206,6 +215,7 @@ fun CharacterDetailContent(
onCharacteristic = onCharacteristic,
onSkill = onSkill,
onUseSkill = onUseSkill,
onAction = onAction,
)
}
}

View file

@ -2,6 +2,7 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetActionUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetCharacteristicUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetSkillUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetUio
@ -192,6 +193,7 @@ class CharacterDetailFactory(
label = skill.label,
value = "$value",
used = skill.used,
occupation = skill.occupation,
tooltips = skill.description?.let {
TooltipUio(
title = skill.label,
@ -217,6 +219,7 @@ class CharacterDetailFactory(
label = skill.label,
value = "$value",
used = skill.used,
occupation = skill.occupation,
tooltips = skill.description?.let {
TooltipUio(
title = skill.label,
@ -231,7 +234,7 @@ class CharacterDetailFactory(
),
)
}.sortedWith(compareBy(Collator.getInstance()) { it.label }),
magicSkill = characterSheet.magicSkills.map { skill ->
magicSkills = characterSheet.magicSkills.map { skill ->
val value = expressionUseCase.computeSkillValue(
sheet = characterSheet,
skill = skill,
@ -242,6 +245,7 @@ class CharacterDetailFactory(
label = skill.label,
value = "$value",
used = skill.used,
occupation = skill.occupation,
tooltips = skill.description?.let {
TooltipUio(
title = skill.label,
@ -256,6 +260,24 @@ class CharacterDetailFactory(
),
)
}.sortedWith(compareBy(Collator.getInstance()) { it.label }),
actions = characterSheet.actions.map { action ->
CharacterDetailSheetActionUio(
actionId = action.id,
label = action.label,
tooltips = action.description?.let {
TooltipUio(
title = action.label,
description = it,
)
},
roll = RollActionUio(
characterSheetId = characterInstanceId.characterSheetId,
label = action.label,
rollAction = action.roll,
rollSuccessValue = null,
)
)
}
)
}
}

View file

@ -1,25 +1,36 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet
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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
import com.pixelized.shared.lwa.model.campaign.Campaign
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__common_title
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__magic_title
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__special_title
import org.jetbrains.compose.resources.stringResource
@Stable
data class CharacterDetailSheetUio(
val characterInstanceId: Campaign.CharacterInstance.Id,
val characteristics: List<CharacterDetailSheetCharacteristicUio>,
val commonSkills: List<CharacterDetailSheetSkillUio>,
val specialSkill: List<CharacterDetailSheetSkillUio>,
val magicSkill: List<CharacterDetailSheetSkillUio>,
val specialSkills: List<CharacterDetailSheetSkillUio>,
val magicSkills: List<CharacterDetailSheetSkillUio>,
val actions: List<CharacterDetailSheetActionUio>,
)
@Composable
@ -29,6 +40,7 @@ fun CharacterDetailSheet(
onCharacteristic: (CharacterDetailSheetCharacteristicUio) -> Unit,
onSkill: (CharacterDetailSheetSkillUio) -> Unit,
onUseSkill: (CharacterDetailSheetSkillUio) -> Unit,
onAction: (CharacterDetailSheetActionUio) -> Unit,
) {
Row(
modifier = modifier,
@ -39,7 +51,7 @@ fun CharacterDetailSheet(
) {
sheet.value?.characteristics?.forEach {
CharacterDetailSheetCharacteristic(
modifier = Modifier.size(width = 80.dp, height = 120.dp),
modifier = Modifier.size(width = 76.dp, height = 110.dp),
characteristic = it,
onClick = { onCharacteristic(it) },
)
@ -47,12 +59,20 @@ fun CharacterDetailSheet(
}
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
DecoratedBox(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
) {
Column {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp, top = 4.dp),
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
text = stringResource(Res.string.character_sheet__skills__common_title),
)
sheet.value?.commonSkills?.forEach { skill ->
CharacterDetailSheetSkill(
modifier = Modifier.fillMaxWidth(),
@ -63,11 +83,22 @@ fun CharacterDetailSheet(
}
}
}
AnimatedVisibility(
visible = sheet.value?.specialSkills?.isNotEmpty() ?: false,
) {
DecoratedBox(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
) {
Column {
sheet.value?.specialSkill?.forEach { skill ->
Text(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp, top = 4.dp),
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
text = stringResource(Res.string.character_sheet__skills__special_title),
)
sheet.value?.specialSkills?.forEach { skill ->
CharacterDetailSheetSkill(
modifier = Modifier.fillMaxWidth(),
skill = skill,
@ -77,11 +108,23 @@ fun CharacterDetailSheet(
}
}
}
}
AnimatedVisibility(
visible = sheet.value?.magicSkills?.isNotEmpty() ?: false,
) {
DecoratedBox(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
) {
Column {
sheet.value?.magicSkill?.forEach { skill ->
Text(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp, top = 4.dp),
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
text = stringResource(Res.string.character_sheet__skills__magic_title),
)
sheet.value?.magicSkills?.forEach { skill ->
CharacterDetailSheetSkill(
modifier = Modifier.fillMaxWidth(),
skill = skill,
@ -92,6 +135,20 @@ fun CharacterDetailSheet(
}
}
}
AnimatedVisibility(
visible = sheet.value?.actions?.isNotEmpty() ?: false,
) {
Column {
sheet.value?.actions?.forEach { action ->
CharacterDetailSheetAction(
modifier = Modifier.fillMaxWidth(),
action = action,
onClick = onAction,
)
}
}
}
}
}
}

View file

@ -0,0 +1,61 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio
import com.pixelized.desktop.lwa.ui.screen.roll.RollActionUio
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp
import org.jetbrains.compose.resources.painterResource
@Stable
data class CharacterDetailSheetActionUio(
val actionId: String,
val label: String,
val tooltips: TooltipUio?,
val roll: RollActionUio,
)
@Composable
fun CharacterDetailSheetAction(
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues(start = 28.dp, end = 9.dp, top = 6.dp, bottom = 6.dp),
action: CharacterDetailSheetActionUio,
onClick: (CharacterDetailSheetActionUio) -> Unit,
) {
Row(
modifier = Modifier
.clickable(onClick = { onClick(action) })
.padding(paddingValues = paddingValues)
.then(other = modifier),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = action.label,
)
Icon(
modifier = Modifier.size(size = 24.dp),
painter = painterResource(Res.drawable.ic_d20_24dp),
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
}
}

View file

@ -5,8 +5,11 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.Checkbox
import androidx.compose.material.CheckboxDefaults
import androidx.compose.material.MaterialTheme
@ -18,6 +21,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.composable.circle.MasteryShape
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipLayout
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio
import com.pixelized.desktop.lwa.ui.screen.roll.RollActionUio
@ -28,6 +32,7 @@ data class CharacterDetailSheetSkillUio(
val label: String,
val value: String,
val used: Boolean,
val occupation: Boolean,
val tooltips: TooltipUio?,
val roll: RollActionUio,
)
@ -36,7 +41,12 @@ data class CharacterDetailSheetSkillUio(
@Composable
fun CharacterDetailSheetSkill(
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues(start = 8.dp),
paddingValues: PaddingValues = PaddingValues(
start = 8.dp,
end = 4.dp,
top = 1.dp,
bottom = 1.dp
),
skill: CharacterDetailSheetSkillUio,
onSkill: (CharacterDetailSheetSkillUio) -> Unit,
onUse: (CharacterDetailSheetSkillUio) -> Unit,
@ -52,6 +62,10 @@ fun CharacterDetailSheetSkill(
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
MasteryShape(
modifier = Modifier.padding(top = 4.dp, end = 2.dp),
multiplier = if (skill.occupation) 1 else 0,
)
Text(
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.body1,

View file

@ -7,10 +7,11 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio
import com.pixelized.desktop.lwa.ui.screen.roll.DifficultyUio.Difficulty
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage
import com.pixelized.shared.lwa.usecase.ExpressionUseCase
import com.pixelized.shared.lwa.usecase.SkillStepUseCase
import kotlinx.coroutines.Job
@ -32,9 +33,9 @@ import org.jetbrains.compose.resources.getString
class RollViewModel(
private val characterSheetRepository: CharacterSheetRepository,
private val rollHistoryRepository: RollHistoryRepository,
private val skillComputation: ExpressionUseCase,
private val skillStepUseCase: SkillStepUseCase,
private val networkRepository: NetworkRepository,
) : ViewModel() {
private lateinit var sheet: CharacterSheet
@ -205,7 +206,7 @@ class RollViewModel(
value = roll,
)
launch {
rollHistoryRepository.share(
val payload = RollMessage(
characterId = sheet.id,
skillLabel = _rollTitle.value.label,
rollDifficulty = when (_rollDifficulty.value?.difficulty) {
@ -219,6 +220,9 @@ class RollViewModel(
rollSuccessLimit = rollStep?.success?.last,
resultLabel = success,
)
networkRepository.share(
payload = payload,
)
}
}
}

View file

@ -4,7 +4,6 @@ import com.pixelized.shared.lwa.protocol.websocket.Message
import io.ktor.websocket.Frame
import io.ktor.websocket.readText
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement
fun Json.decodeFromFrame(frame: Frame.Text): Message {
val json = frame.readText()
@ -12,6 +11,6 @@ fun Json.decodeFromFrame(frame: Frame.Text): Message {
}
fun Json.encodeToFrame(message: Message): Frame {
val json = encodeToJsonElement(message)
return Frame.Text(text = json.toString())
val json = encodeToString(message)
return Frame.Text(text = json)
}

View file

@ -0,0 +1,195 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pixelized.desktop.lwa.utils.extention
import androidx.compose.foundation.BorderStroke
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.geometry.isSimple
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.unit.Dp
/**
* Modify element to add border with appearance specified with a [border] and a [shape], pad the
* content by the [BorderStroke.width] and clip it.
*
* @sample androidx.compose.foundation.samples.BorderSample()
*
* @param border [BorderStroke] class that specifies border appearance, such as size and color
* @param shape shape of the border
*/
fun Modifier.dashedBorder(border: BorderStroke, shape: Shape = RectangleShape, on: Dp, off: Dp) =
dashedBorder(width = border.width, brush = border.brush, shape = shape, on, off)
/**
* Returns a [Modifier] that adds border with appearance specified with [width], [color] and a
* [shape], pads the content by the [width] and clips it.
*
* @sample androidx.compose.foundation.samples.BorderSampleWithDataClass()
*
* @param width width of the border. Use [Dp.Hairline] for a hairline border.
* @param color color to paint the border with
* @param shape shape of the border
* @param on the size of the solid part of the dashes
* @param off the size of the space between dashes
*/
fun Modifier.dashedBorder(width: Dp, color: Color, shape: Shape = RectangleShape, on: Dp, off: Dp) =
dashedBorder(width, SolidColor(color), shape, on, off)
/**
* Returns a [Modifier] that adds border with appearance specified with [width], [brush] and a
* [shape], pads the content by the [width] and clips it.
*
* @sample androidx.compose.foundation.samples.BorderSampleWithBrush()
*
* @param width width of the border. Use [Dp.Hairline] for a hairline border.
* @param brush brush to paint the border with
* @param shape shape of the border
*/
fun Modifier.dashedBorder(width: Dp, brush: Brush, shape: Shape, on: Dp, off: Dp): Modifier =
this.then(
Modifier.drawWithCache {
val outline: Outline = shape.createOutline(size, layoutDirection, this)
val borderSize = if (width == Dp.Hairline) 1f else width.toPx()
var insetOutline: Outline? = null // outline used for roundrect/generic shapes
var stroke: Stroke? = null // stroke to draw border for all outline types
var pathClip: Path? = null // path to clip roundrect/generic shapes
var inset = 0f // inset to translate before drawing the inset outline
// path to draw generic shapes or roundrects with different corner radii
var insetPath: Path? = null
if (borderSize > 0 && size.minDimension > 0f) {
if (outline is Outline.Rectangle) {
stroke = Stroke(
borderSize, pathEffect = PathEffect.dashPathEffect(
floatArrayOf(on.toPx(), off.toPx())
)
)
} else {
// Multiplier to apply to the border size to get a stroke width that is
// large enough to cover the corners while not being too large to overly
// square off the internal shape. The resultant shape will be
// clipped to the desired shape. Any value lower will show artifacts in
// the corners of shapes. A value too large will always square off
// the internal shape corners. For example, for a rounded rect border
// a large multiplier will always have squared off edges within the
// inner section of the stroke, however, having a smaller multiplier
// will still keep the rounded effect for the inner section of the
// border
val strokeWidth = 1.2f * borderSize
inset = borderSize - strokeWidth / 2
val insetSize = Size(
size.width - inset * 2,
size.height - inset * 2
)
insetOutline = shape.createOutline(insetSize, layoutDirection, this)
stroke = Stroke(
strokeWidth, pathEffect = PathEffect.dashPathEffect(
floatArrayOf(on.toPx(), off.toPx())
)
)
pathClip = when (outline) {
is Outline.Rounded -> {
Path().apply { addRoundRect(outline.roundRect) }
}
is Outline.Generic -> {
outline.path
}
// should not get here because we check for Outline.Rectangle above
else -> {
null
}
}
insetPath = when {
// Rounded rect with non equal corner radii needs a path to be pre-translated
insetOutline is Outline.Rounded && !insetOutline.roundRect.isSimple -> {
Path().apply {
addRoundRect(insetOutline.roundRect)
translate(Offset(inset, inset))
}
}
// Generic paths must be created and pre-translated
insetOutline is Outline.Generic -> {
Path().apply {
addPath(insetOutline.path, Offset(inset, inset))
}
}
// Drawing a round rect with equal corner radii without usage of a path
else -> {
null
}
}
}
}
onDrawWithContent {
drawContent()
// Only draw the border if a have a valid stroke parameter. If we have
// an invalid border size we will just draw the content
if (stroke != null) {
if (insetOutline != null && pathClip != null) {
val isSimpleRoundRect =
insetOutline is Outline.Rounded && insetOutline.roundRect.isSimple
withTransform({
clipPath(pathClip)
// we are drawing the round rect not as a path so we must
// translate ourselves othe
if (isSimpleRoundRect) {
translate(inset, inset)
}
}) {
if (isSimpleRoundRect) {
// If we don't have an insetPath then we are drawing
// a simple round rect with the corner radii all identical
val rrect = (insetOutline as Outline.Rounded).roundRect
drawRoundRect(
brush = brush,
topLeft = Offset(rrect.left, rrect.top),
size = Size(rrect.width, rrect.height),
cornerRadius = rrect.topLeftCornerRadius,
style = stroke
)
} else if (insetPath != null) {
drawPath(insetPath, brush, style = stroke)
}
}
} else {
// Rectangular border fast path
val strokeWidth = stroke.width
val halfStrokeWidth = strokeWidth / 2
drawRect(
brush = brush,
topLeft = Offset(halfStrokeWidth, halfStrokeWidth),
size = Size(
size.width - strokeWidth,
size.height - strokeWidth
),
style = stroke
)
}
}
}
}
)

View file

@ -17,6 +17,7 @@ dependencies {
implementation(projects.shared)
implementation(libs.logback)
implementation(libs.koin.ktor)
implementation(libs.kotlinx.serialization.json)
implementation(libs.ktor.server.core)
implementation(libs.ktor.server.netty)
implementation(libs.ktor.server.websockets)

View file

@ -4,7 +4,7 @@ import com.pixelized.shared.lwa.protocol.websocket.Message
import io.ktor.websocket.Frame
import io.ktor.websocket.readText
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement
fun Json.decodeFromFrame(frame: Frame.Text): Message {
val json = frame.readText()
@ -12,6 +12,6 @@ fun Json.decodeFromFrame(frame: Frame.Text): Message {
}
fun Json.encodeToFrame(message: Message): Frame {
val json = encodeToJsonElement(message)
return Frame.Text(text = json.toString())
val json = encodeToString(message)
return Frame.Text(text = json)
}

View file

@ -81,7 +81,8 @@ class LocalServer {
val job = launch {
// send local message to the clients
engine.webSocket.collect { message ->
send(json.encodeToFrame(message))
val frame = json.encodeToFrame(message)
send(frame)
}
}
runCatching {

View file

@ -6,8 +6,8 @@ import kotlinx.serialization.Serializable
data class RollMessage(
val characterId: String,
val skillLabel: String,
val resultLabel: String?,
val rollDifficulty: String?,
val resultLabel: String? = null,
val rollDifficulty: String? = null,
val rollValue: Int,
val rollSuccessLimit: Int?,
val rollSuccessLimit: Int? = null,
) : MessagePayload