Refactor the Character Ribbon (levelup)
This commit is contained in:
		
							parent
							
								
									24fe030663
								
							
						
					
					
						commit
						6364201c6c
					
				
					 14 changed files with 457 additions and 331 deletions
				
			
		|  | @ -38,8 +38,9 @@ kotlin { | ||||||
|             // composable component. |             // composable component. | ||||||
|             implementation(libs.coil.compose) |             implementation(libs.coil.compose) | ||||||
|             implementation(libs.coil.network.ktor) |             implementation(libs.coil.network.ktor) | ||||||
| //            implementation("com.mikepenz.hypnoticcanvas:hypnoticcanvas:0.3.0") |             // Shader | ||||||
| //            implementation("com.mikepenz.hypnoticcanvas:hypnoticcanvas-shaders:0.3.0") |             implementation(libs.hypnoticcanvas) | ||||||
|  |             implementation(libs.hypnoticcanvas.shaders) | ||||||
|             // network |             // network | ||||||
|             implementation(libs.kotlinx.serialization.json) |             implementation(libs.kotlinx.serialization.json) | ||||||
|             implementation(libs.ktor.serialization.json) |             implementation(libs.ktor.serialization.json) | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.graphics.Color | import androidx.compose.ui.graphics.Color | ||||||
| import androidx.compose.ui.unit.dp | import androidx.compose.ui.unit.dp | ||||||
| import androidx.compose.ui.window.Dialog | import androidx.compose.ui.window.Dialog | ||||||
|  | import androidx.compose.ui.window.DialogProperties | ||||||
| import com.pixelized.desktop.lwa.LocalBlurController | import com.pixelized.desktop.lwa.LocalBlurController | ||||||
| import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController | import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController | ||||||
| import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox | import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox | ||||||
|  | @ -32,6 +33,7 @@ object LwaDialogDefault { | ||||||
| @Composable | @Composable | ||||||
| fun <T> LwaDialog( | fun <T> LwaDialog( | ||||||
|     modifier: Modifier = Modifier, |     modifier: Modifier = Modifier, | ||||||
|  |     properties: DialogProperties = DialogProperties(), | ||||||
|     blur: BlurContentController? = LocalBlurController.current, |     blur: BlurContentController? = LocalBlurController.current, | ||||||
|     paddings: PaddingValues = LwaDialogDefault.paddings, |     paddings: PaddingValues = LwaDialogDefault.paddings, | ||||||
|     color: Color = MaterialTheme.colors.surface, |     color: Color = MaterialTheme.colors.surface, | ||||||
|  | @ -52,6 +54,7 @@ fun <T> LwaDialog( | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Dialog( |         Dialog( | ||||||
|  |             properties = properties, | ||||||
|             onDismissRequest = onDismissRequest, |             onDismissRequest = onDismissRequest, | ||||||
|             content = { |             content = { | ||||||
|                 Box( |                 Box( | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Column | ||||||
| import androidx.compose.foundation.layout.PaddingValues | import androidx.compose.foundation.layout.PaddingValues | ||||||
| import androidx.compose.foundation.layout.Row | 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.fillMaxWidth | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
| import androidx.compose.foundation.layout.padding | import androidx.compose.foundation.layout.padding | ||||||
| import androidx.compose.foundation.layout.width | import androidx.compose.foundation.layout.width | ||||||
|  | @ -19,22 +18,21 @@ import androidx.compose.material.Surface | ||||||
| import androidx.compose.material.Text | import androidx.compose.material.Text | ||||||
| import androidx.compose.material.minimumInteractiveComponentSize | import androidx.compose.material.minimumInteractiveComponentSize | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.runtime.DisposableEffect |  | ||||||
| import androidx.compose.runtime.Stable | import androidx.compose.runtime.Stable | ||||||
| import androidx.compose.runtime.State | import androidx.compose.runtime.State | ||||||
| import androidx.compose.runtime.collectAsState |  | ||||||
| import androidx.compose.ui.Alignment | import androidx.compose.ui.Alignment | ||||||
| import androidx.compose.ui.ExperimentalComposeUiApi | import androidx.compose.ui.ExperimentalComposeUiApi | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.unit.dp | import androidx.compose.ui.unit.dp | ||||||
| import androidx.compose.ui.window.Dialog |  | ||||||
| import androidx.compose.ui.window.DialogProperties | import androidx.compose.ui.window.DialogProperties | ||||||
| import com.pixelized.desktop.lwa.LocalBlurController | import androidx.compose.ui.zIndex | ||||||
| import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController | import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||||||
|  | import com.pixelized.desktop.lwa.ui.composable.character.LwaDialog | ||||||
| import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio | import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio | ||||||
| import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMFilterHeader | import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMFilterHeader | ||||||
| import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio | import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio | ||||||
| import com.pixelized.desktop.lwa.ui.theme.lwa | import com.pixelized.desktop.lwa.ui.theme.lwa | ||||||
|  | import com.pixelized.desktop.lwa.utils.extention.calculatePaddings | ||||||
| import kotlinx.coroutines.flow.StateFlow | import kotlinx.coroutines.flow.StateFlow | ||||||
| import lwacharactersheet.composeapp.generated.resources.Res | import lwacharactersheet.composeapp.generated.resources.Res | ||||||
| import lwacharactersheet.composeapp.generated.resources.ic_close_24dp | import lwacharactersheet.composeapp.generated.resources.ic_close_24dp | ||||||
|  | @ -52,113 +50,112 @@ data class CharacterSheetAlterationDialogUio( | ||||||
| @OptIn(ExperimentalComposeUiApi::class) | @OptIn(ExperimentalComposeUiApi::class) | ||||||
| @Composable | @Composable | ||||||
| fun CharacterSheetAlterationDialog( | fun CharacterSheetAlterationDialog( | ||||||
|     blur: BlurContentController? = LocalBlurController.current, |  | ||||||
|     dialog: State<CharacterSheetAlterationDialogUio?>, |     dialog: State<CharacterSheetAlterationDialogUio?>, | ||||||
|     onTag: (String) -> Unit, |     onTag: (String) -> Unit, | ||||||
|     onAlteration: (characterSheetId: String, alterationId: String, active: Boolean) -> Unit, |     onAlteration: (characterSheetId: String, alterationId: String, active: Boolean) -> Unit, | ||||||
|     onDismissRequest: () -> Unit, |     onDismissRequest: () -> Unit, | ||||||
| ) { | ) { | ||||||
|     dialog.value?.let { |     LwaDialog( | ||||||
| 
 |         properties = DialogProperties( | ||||||
|         blur?.let { |             usePlatformDefaultWidth = false, | ||||||
|             DisposableEffect("LwaDialog") { |             usePlatformInsets = false, | ||||||
|                 blur.show() |         ), | ||||||
|                 onDispose { |         state = dialog, | ||||||
|                     blur.hide() |         onDismissRequest = onDismissRequest, | ||||||
|                 } |         onConfirm = onDismissRequest, | ||||||
|             } |         content = { | ||||||
|  |             CharacterSheetAlterationContent( | ||||||
|  |                 dialog = it, | ||||||
|  |                 onTag = onTag, | ||||||
|  |                 onAlteration = onAlteration, | ||||||
|  |                 onDismissRequest = onDismissRequest, | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
| 
 |     ) | ||||||
|         Dialog( |  | ||||||
|             properties = DialogProperties( |  | ||||||
|                 usePlatformDefaultWidth = false, |  | ||||||
|                 usePlatformInsets = false, |  | ||||||
|             ), |  | ||||||
|             onDismissRequest = onDismissRequest, |  | ||||||
|             content = { |  | ||||||
|                 CharacterSheetAlterationContent( |  | ||||||
|                     dialog = it, |  | ||||||
|                     onTag = onTag, |  | ||||||
|                     onAlteration = onAlteration, |  | ||||||
|                     onDismissRequest = onDismissRequest, |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun CharacterSheetAlterationContent( | fun CharacterSheetAlterationContent( | ||||||
|     dialog: CharacterSheetAlterationDialogUio, |     dialog: CharacterSheetAlterationDialogUio, | ||||||
|  |     paddingValues: PaddingValues = MaterialTheme.lwa.dimen.paddingValues, | ||||||
|     onTag: (String) -> Unit, |     onTag: (String) -> Unit, | ||||||
|     onAlteration: (characterSheetId: String, alterationId: String, active: Boolean) -> Unit, |     onAlteration: (characterSheetId: String, alterationId: String, active: Boolean) -> Unit, | ||||||
|     onDismissRequest: () -> Unit, |     onDismissRequest: () -> Unit, | ||||||
| ) { | ) { | ||||||
|     Surface( |     val padding = MaterialTheme.lwa.dimen.paddingValue | ||||||
|  |     val (start, _, end, _) = paddingValues.calculatePaddings() | ||||||
|  | 
 | ||||||
|  |     Column( | ||||||
|         modifier = Modifier |         modifier = Modifier | ||||||
|             .fillMaxHeight() |             .fillMaxHeight() | ||||||
|             .width(width = 128.dp * 4) |             .width(width = 128.dp * 4) | ||||||
|             .padding(vertical = 16.dp), |  | ||||||
|     ) { |     ) { | ||||||
|         Column( |         // Add a surface and a zIndex() to avoid the animation coming from : .animateItem() (see below) | ||||||
|             modifier = Modifier.fillMaxSize(), |         // to draw over this part of the dialog. | ||||||
|  |         Surface( | ||||||
|  |             modifier = Modifier.zIndex(1f) | ||||||
|         ) { |         ) { | ||||||
|             Row( |             Column { | ||||||
|                 modifier = Modifier.padding(start = 16.dp).fillMaxWidth(), |                 Row( | ||||||
|                 verticalAlignment = Alignment.CenterVertically, |                     modifier = Modifier | ||||||
|                 horizontalArrangement = Arrangement.SpaceBetween, |                         .padding(start = start) | ||||||
|             ) { |                         .fillMaxWidth(), | ||||||
|                 Text( |                     verticalAlignment = Alignment.CenterVertically, | ||||||
|                     style = MaterialTheme.typography.h6, |                     horizontalArrangement = Arrangement.SpaceBetween, | ||||||
|                     color = MaterialTheme.colors.onSurface, |  | ||||||
|                     text = dialog.characterName, |  | ||||||
|                 ) |  | ||||||
|                 IconButton( |  | ||||||
|                     onClick = onDismissRequest, |  | ||||||
|                 ) { |                 ) { | ||||||
|                     Icon( |                     Text( | ||||||
|                         painter = painterResource(Res.drawable.ic_close_24dp), |                         style = MaterialTheme.typography.h6, | ||||||
|                         tint = MaterialTheme.lwa.colorScheme.base.primary, |                         color = MaterialTheme.colors.onSurface, | ||||||
|                         contentDescription = null, |                         text = dialog.characterName, | ||||||
|                     ) |                     ) | ||||||
|  |                     IconButton( | ||||||
|  |                         onClick = onDismissRequest, | ||||||
|  |                     ) { | ||||||
|  |                         Icon( | ||||||
|  |                             painter = painterResource(Res.drawable.ic_close_24dp), | ||||||
|  |                             tint = MaterialTheme.lwa.colorScheme.base.primary, | ||||||
|  |                             contentDescription = null, | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|  |                 GMFilterHeader( | ||||||
|  |                     filter = dialog.filter, | ||||||
|  |                     padding = padding, | ||||||
|  |                     tags = dialog.tags.collectAsStateWithLifecycle(), | ||||||
|  |                     onTag = onTag, | ||||||
|  |                 ) | ||||||
|             } |             } | ||||||
|             GMFilterHeader( |         } | ||||||
|                 filter = dialog.filter, |         LazyColumn( | ||||||
|                 tags = dialog.tags.collectAsState(), |             modifier = Modifier | ||||||
|                 onTag = onTag, |                 .fillMaxWidth() | ||||||
|             ) |                 .weight(weight = 1f, fill = true), | ||||||
|             LazyColumn( |             contentPadding = paddingValues, | ||||||
|                 modifier = Modifier |             verticalArrangement = Arrangement.spacedBy(space = padding), | ||||||
|                     .fillMaxWidth() |         ) { | ||||||
|                     .weight(weight = 1f), |             items( | ||||||
|                 contentPadding = PaddingValues(start = 8.dp, end = 8.dp, bottom = 8.dp), |                 items = dialog.alterations, | ||||||
|                 verticalArrangement = Arrangement.spacedBy(space = 8.dp), |                 key = { it.id }, | ||||||
|             ) { |             ) { alteration -> | ||||||
|                 items( |                 AlterationToggleItem( | ||||||
|                     items = dialog.alterations, |                     modifier = Modifier | ||||||
|                     key = { it.id } |                         .animateItem() | ||||||
|                 ) { alteration -> |                         .background( | ||||||
|                     AlterationToggleItem( |                             color = MaterialTheme.lwa.colorScheme.elevated.base1dp, | ||||||
|                         modifier = Modifier |                             shape = MaterialTheme.lwa.shapes.base.small, | ||||||
|                             .animateItem() |                         ) | ||||||
|                             .background( |                         .minimumInteractiveComponentSize() | ||||||
|                                 color = MaterialTheme.lwa.colorScheme.elevated.base1dp, |                         .padding(start = start, end = end), | ||||||
|                                 shape = MaterialTheme.lwa.shapes.base.small, |                     alteration = alteration, | ||||||
|                             ) |                     onAlteration = { | ||||||
|                             .minimumInteractiveComponentSize() |                         onAlteration( | ||||||
|                             .padding(horizontal = 8.dp), |                             dialog.characterSheetId, | ||||||
|                         alteration = alteration, |                             alteration.id, | ||||||
|                         onAlteration = { |                             alteration.active, | ||||||
|                             onAlteration( |                         ) | ||||||
|                                 dialog.characterSheetId, |                     }, | ||||||
|                                 alteration.id, |                     onTag = onTag, | ||||||
|                                 alteration.active, |                 ) | ||||||
|                             ) |  | ||||||
|                         }, |  | ||||||
|                         onTag = onTag, |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -34,7 +34,6 @@ import androidx.compose.ui.unit.Density | ||||||
| import androidx.compose.ui.unit.DpSize | import androidx.compose.ui.unit.DpSize | ||||||
| import androidx.compose.ui.unit.IntSize | import androidx.compose.ui.unit.IntSize | ||||||
| import androidx.compose.ui.unit.LayoutDirection | import androidx.compose.ui.unit.LayoutDirection | ||||||
| import androidx.compose.ui.unit.dp |  | ||||||
| import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent | import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent | ||||||
| import com.pixelized.desktop.lwa.ui.composable.blur.rememberBlurContentController | import com.pixelized.desktop.lwa.ui.composable.blur.rememberBlurContentController | ||||||
| import com.pixelized.desktop.lwa.ui.composable.character.alterteration.CharacterSheetAlterationDialog | import com.pixelized.desktop.lwa.ui.composable.character.alterteration.CharacterSheetAlterationDialog | ||||||
|  | @ -143,9 +142,6 @@ fun CampaignScreen( | ||||||
|                                 ) |                                 ) | ||||||
|                             } |                             } | ||||||
|                         }, |                         }, | ||||||
|                         onLevelUp = { |  | ||||||
|                             screen.navigateToLevelScreen(characterSheetId = it) |  | ||||||
|                         } |  | ||||||
|                     ) |                     ) | ||||||
|                 }, |                 }, | ||||||
|                 rightPanel = { |                 rightPanel = { | ||||||
|  | @ -169,9 +165,6 @@ fun CampaignScreen( | ||||||
|                                 ) |                                 ) | ||||||
|                             } |                             } | ||||||
|                         }, |                         }, | ||||||
|                         onLevelUp = { |  | ||||||
|                             screen.navigateToLevelScreen(characterSheetId = it) |  | ||||||
|                         } |  | ||||||
|                     ) |                     ) | ||||||
|                 }, |                 }, | ||||||
|                 leftOverlay = { |                 leftOverlay = { | ||||||
|  |  | ||||||
|  | @ -1,10 +1,16 @@ | ||||||
| package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon | package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon | ||||||
| 
 | 
 | ||||||
|  | import androidx.compose.animation.AnimatedVisibility | ||||||
|  | import androidx.compose.animation.fadeIn | ||||||
|  | import androidx.compose.animation.fadeOut | ||||||
| import androidx.compose.foundation.ExperimentalFoundationApi | import androidx.compose.foundation.ExperimentalFoundationApi | ||||||
| import androidx.compose.foundation.PointerMatcher | import androidx.compose.foundation.PointerMatcher | ||||||
|  | import androidx.compose.foundation.border | ||||||
| import androidx.compose.foundation.layout.Arrangement | import androidx.compose.foundation.layout.Arrangement | ||||||
| import androidx.compose.foundation.layout.Box | import androidx.compose.foundation.layout.Box | ||||||
|  | import androidx.compose.foundation.layout.Column | ||||||
| import androidx.compose.foundation.layout.PaddingValues | import androidx.compose.foundation.layout.PaddingValues | ||||||
|  | import androidx.compose.foundation.layout.size | ||||||
| import androidx.compose.foundation.lazy.LazyColumn | import androidx.compose.foundation.lazy.LazyColumn | ||||||
| import androidx.compose.foundation.lazy.items | import androidx.compose.foundation.lazy.items | ||||||
| import androidx.compose.foundation.onClick | import androidx.compose.foundation.onClick | ||||||
|  | @ -13,13 +19,22 @@ import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.runtime.CompositionLocalProvider | import androidx.compose.runtime.CompositionLocalProvider | ||||||
| import androidx.compose.runtime.collectAsState | import androidx.compose.runtime.collectAsState | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.draw.clip | ||||||
|  | import androidx.compose.ui.graphics.Color | ||||||
|  | import androidx.compose.ui.graphics.Shape | ||||||
| import androidx.compose.ui.graphics.graphicsLayer | import androidx.compose.ui.graphics.graphicsLayer | ||||||
| import androidx.compose.ui.input.pointer.PointerButton | import androidx.compose.ui.input.pointer.PointerButton | ||||||
| import androidx.compose.ui.platform.LocalLayoutDirection | import androidx.compose.ui.platform.LocalLayoutDirection | ||||||
|  | import androidx.compose.ui.unit.DpSize | ||||||
| import androidx.compose.ui.unit.LayoutDirection | import androidx.compose.ui.unit.LayoutDirection | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | import com.lordcodes.turtle.Arguments | ||||||
| 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.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.CharacterRibbonStats | ||||||
|  | import com.pixelized.desktop.lwa.ui.shaders.packs.GoldenFlow | ||||||
|  | import com.pixelized.desktop.lwa.ui.shaders.shaderBorder | ||||||
| import com.pixelized.desktop.lwa.ui.theme.lwa | import com.pixelized.desktop.lwa.ui.theme.lwa | ||||||
| import com.pixelized.desktop.lwa.utils.extention.invert | import com.pixelized.desktop.lwa.utils.extention.invert | ||||||
| 
 | 
 | ||||||
|  | @ -29,10 +44,11 @@ fun CharacterRibbon( | ||||||
|     modifier: Modifier = Modifier, |     modifier: Modifier = Modifier, | ||||||
|     layoutDirection: LayoutDirection, |     layoutDirection: LayoutDirection, | ||||||
|     viewModel: CharacterRibbonViewModel, |     viewModel: CharacterRibbonViewModel, | ||||||
|  |     shape: Shape = MaterialTheme.lwa.shapes.portrait, | ||||||
|  |     size: DpSize = MaterialTheme.lwa.dimen.portrait.minimized, | ||||||
|     padding: PaddingValues = MaterialTheme.lwa.dimen.paddingValues, |     padding: PaddingValues = MaterialTheme.lwa.dimen.paddingValues, | ||||||
|     onCharacterLeftClick: (characterSheetId: String) -> Unit, |     onCharacterLeftClick: (characterSheetId: String) -> Unit, | ||||||
|     onCharacterRightClick: (characterSheetId: String) -> Unit, |     onCharacterRightClick: (characterSheetId: String) -> Unit, | ||||||
|     onLevelUp: (characterSheetId: String) -> Unit, |  | ||||||
| ) { | ) { | ||||||
|     val characters = viewModel.characters.collectAsState() |     val characters = viewModel.characters.collectAsState() | ||||||
| 
 | 
 | ||||||
|  | @ -53,6 +69,7 @@ fun CharacterRibbon( | ||||||
|                 Box( |                 Box( | ||||||
|                     modifier = Modifier |                     modifier = Modifier | ||||||
|                         .animateItem() |                         .animateItem() | ||||||
|  |                         .clip(shape = shape) | ||||||
|                         .graphicsLayer { if (it.hideOverruled) this.alpha = 0.3f } |                         .graphicsLayer { if (it.hideOverruled) this.alpha = 0.3f } | ||||||
|                         .onClick( |                         .onClick( | ||||||
|                             matcher = PointerMatcher.mouse(PointerButton.Primary), |                             matcher = PointerMatcher.mouse(PointerButton.Primary), | ||||||
|  | @ -66,19 +83,38 @@ fun CharacterRibbon( | ||||||
|                         ), |                         ), | ||||||
|                 ) { |                 ) { | ||||||
|                     CharacterRibbonPortrait( |                     CharacterRibbonPortrait( | ||||||
|  |                         size = size, | ||||||
|                         character = it.portrait, |                         character = it.portrait, | ||||||
|                         onLevelUp = { onLevelUp(it.characterSheetId) }, |  | ||||||
|                     ) |                     ) | ||||||
|                     CompositionLocalProvider( |                     Column( | ||||||
|                         LocalLayoutDirection provides layoutDirection.invert |                         modifier = Modifier.size(size), | ||||||
|  |                         verticalArrangement = Arrangement.SpaceBetween, | ||||||
|                     ) { |                     ) { | ||||||
|                         CharacterRibbonAlteration( |                         CompositionLocalProvider( | ||||||
|  |                             LocalLayoutDirection provides layoutDirection.invert | ||||||
|  |                         ) { | ||||||
|  |                             CharacterRibbonAlteration( | ||||||
|  |                                 alterations = it.alterations, | ||||||
|  |                             ) | ||||||
|  |                         } | ||||||
|  |                         CharacterRibbonStats( | ||||||
|                             status = it.status, |                             status = it.status, | ||||||
|                         ) |                         ) | ||||||
|                     } |                     } | ||||||
|                     CharacterRibbonRoll( |                     CharacterRibbonRoll( | ||||||
|                         value = viewModel.roll(characterSheetId = it.characterSheetId).value, |                         value = viewModel.roll(characterSheetId = it.characterSheetId).value, | ||||||
|                     ) |                     ) | ||||||
|  |                     AnimatedVisibility( | ||||||
|  |                         visible = it.levelUp, | ||||||
|  |                         enter = fadeIn(), | ||||||
|  |                         exit = fadeOut() | ||||||
|  |                     ) { | ||||||
|  |                         Box( | ||||||
|  |                             modifier = Modifier | ||||||
|  |                                 .size(size) | ||||||
|  |                                 .shaderBorder(width = 2.dp, shape = shape, shader = GoldenFlow) | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -3,6 +3,8 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon | ||||||
| import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipUio | import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipUio | ||||||
| import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlterationUio | import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlterationUio | ||||||
| import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortraitUio | import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortraitUio | ||||||
|  | import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonStats | ||||||
|  | import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonStatsUio | ||||||
| import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonUio | import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonUio | ||||||
| import com.pixelized.desktop.lwa.utils.extention.foldInto | import com.pixelized.desktop.lwa.utils.extention.foldInto | ||||||
| import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory | import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory | ||||||
|  | @ -35,20 +37,25 @@ class CharacterRibbonFactory( | ||||||
|             characterSheetId = characterSheet.id, |             characterSheetId = characterSheet.id, | ||||||
|             hideOverruled = hideOverruled, |             hideOverruled = hideOverruled, | ||||||
|             enableDetail = enableCharacterSheet, |             enableDetail = enableCharacterSheet, | ||||||
|  |             levelUp = alteredCharacterSheet.shouldLevelUp, | ||||||
|             portrait = CharacterRibbonPortraitUio( |             portrait = CharacterRibbonPortraitUio( | ||||||
|                 portrait = alteredCharacterSheet.thumbnail, |                 portrait = alteredCharacterSheet.thumbnail, | ||||||
|                 name = alteredCharacterSheet.name, |                 name = alteredCharacterSheet.name, | ||||||
|                 levelUp = alteredCharacterSheet.shouldLevelUp, |                 damagePercent = takeIf { enableCharacterStats }?.let { | ||||||
|                 stats = takeIf { enableCharacterStats }?.let { |                     val maxHp = alteredCharacterSheet.maxHp.toFloat() | ||||||
|                     CharacterRibbonPortraitUio.StatsDetail( |                     val damage = alteredCharacterSheet.damage.toFloat() | ||||||
|                         hp = alteredCharacterSheet.maxHp - alteredCharacterSheet.damage, |                     1f - ((maxHp - damage) / maxHp).coerceIn(0f, 1f) | ||||||
|                         maxHp = alteredCharacterSheet.maxHp, |  | ||||||
|                         pp = alteredCharacterSheet.maxPp - alteredCharacterSheet.fatigue, |  | ||||||
|                         maxPp = alteredCharacterSheet.maxPp, |  | ||||||
|                     ) |  | ||||||
|                 }, |                 }, | ||||||
|             ), |             ), | ||||||
|             status = takeIf { enableCharacterStatus }?.let { |             status = takeIf { enableCharacterStats }?.let { | ||||||
|  |                 CharacterRibbonStatsUio( | ||||||
|  |                     hp = alteredCharacterSheet.maxHp - alteredCharacterSheet.damage, | ||||||
|  |                     maxHp = alteredCharacterSheet.maxHp, | ||||||
|  |                     pp = alteredCharacterSheet.maxPp - alteredCharacterSheet.fatigue, | ||||||
|  |                     maxPp = alteredCharacterSheet.maxPp, | ||||||
|  |                 ) | ||||||
|  |             }, | ||||||
|  |             alterations = takeIf { enableCharacterStatus }?.let { | ||||||
|                 alterations.map { alteration -> |                 alterations.map { alteration -> | ||||||
|                     CharacterRibbonAlterationUio( |                     CharacterRibbonAlterationUio( | ||||||
|                         icon = alteration.metadata.icon ?: DEFAULT_ICON, |                         icon = alteration.metadata.icon ?: DEFAULT_ICON, | ||||||
|  |  | ||||||
|  | @ -13,22 +13,19 @@ import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.graphics.Color | import androidx.compose.ui.graphics.Color | ||||||
| import com.pixelized.desktop.lwa.ui.theme.lwa | import com.pixelized.desktop.lwa.ui.theme.lwa | ||||||
| import kotlin.math.max |  | ||||||
| import kotlin.math.min |  | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun BloodOverlay( | fun BloodOverlay( | ||||||
|     modifier: Modifier = Modifier, |     modifier: Modifier = Modifier, | ||||||
|     bloodColor: Color = MaterialTheme.lwa.colorScheme.portrait.blood, |     bloodColor: Color = MaterialTheme.lwa.colorScheme.portrait.blood, | ||||||
|     maxHp: Float, |     damagePercent: Float, | ||||||
|     hp: Float, |  | ||||||
| ) { | ) { | ||||||
|     val animatedRatio = animateFloatAsState( |     val animatedRatio = animateFloatAsState( | ||||||
|         targetValue = min(maxHp, max(0f, (maxHp - hp) / maxHp)), |         targetValue = damagePercent, | ||||||
|         animationSpec = tween(durationMillis = 350, easing = EaseOutCirc) |         animationSpec = tween(durationMillis = 350, easing = EaseOutCirc) | ||||||
|     ) |     ) | ||||||
|     val animatedColor = animateColorAsState( |     val animatedColor = animateColorAsState( | ||||||
|         targetValue = bloodColor.copy(alpha = ((maxHp - hp) / maxHp) / 4f + .25f) |         targetValue = bloodColor.copy(alpha = damagePercent / 4f + .25f) | ||||||
|     ) |     ) | ||||||
|     Box( |     Box( | ||||||
|         modifier = modifier |         modifier = modifier | ||||||
|  |  | ||||||
|  | @ -1,10 +1,5 @@ | ||||||
| package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common | package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common | ||||||
| 
 | 
 | ||||||
| import androidx.compose.animation.AnimatedContent |  | ||||||
| import androidx.compose.animation.animateContentSize |  | ||||||
| import androidx.compose.animation.fadeIn |  | ||||||
| import androidx.compose.animation.fadeOut |  | ||||||
| import androidx.compose.animation.togetherWith |  | ||||||
| import androidx.compose.foundation.ExperimentalFoundationApi | import androidx.compose.foundation.ExperimentalFoundationApi | ||||||
| import androidx.compose.foundation.TooltipPlacement | import androidx.compose.foundation.TooltipPlacement | ||||||
| import androidx.compose.foundation.layout.Arrangement | import androidx.compose.foundation.layout.Arrangement | ||||||
|  | @ -42,19 +37,18 @@ data class CharacterRibbonAlterationUio( | ||||||
| fun CharacterRibbonAlteration( | fun CharacterRibbonAlteration( | ||||||
|     modifier: Modifier = Modifier, |     modifier: Modifier = Modifier, | ||||||
|     width: Dp = MaterialTheme.lwa.dimen.portrait.minimized.width, |     width: Dp = MaterialTheme.lwa.dimen.portrait.minimized.width, | ||||||
|     status: List<List<CharacterRibbonAlterationUio>>, |     alterations: List<List<CharacterRibbonAlterationUio>>, | ||||||
| ) { | ) { | ||||||
|     val direction = LocalLayoutDirection.current |     val direction = LocalLayoutDirection.current | ||||||
|  | 
 | ||||||
|     Row( |     Row( | ||||||
|         modifier = Modifier |         modifier = Modifier | ||||||
|             .animateContentSize() |  | ||||||
|             .width(width = width) |             .width(width = width) | ||||||
|             .then(other = modifier), |             .then(other = modifier), | ||||||
|         horizontalArrangement = Arrangement.spacedBy(space = 4.dp), |         horizontalArrangement = Arrangement.spacedBy(space = 4.dp), | ||||||
|     ) { |     ) { | ||||||
|         status.forEach { columns -> |         alterations.forEach { columns -> | ||||||
|             Column( |             Column( | ||||||
|                 modifier = Modifier.animateContentSize(), |  | ||||||
|                 verticalArrangement = Arrangement.spacedBy(space = 2.dp), |                 verticalArrangement = Arrangement.spacedBy(space = 2.dp), | ||||||
|             ) { |             ) { | ||||||
|                 columns.forEach { |                 columns.forEach { | ||||||
|  | @ -70,20 +64,15 @@ fun CharacterRibbonAlteration( | ||||||
|                             ) |                             ) | ||||||
|                         }, |                         }, | ||||||
|                         content = { |                         content = { | ||||||
|                             AnimatedContent( |                             LwaAsyncImage( | ||||||
|                                 targetState = it.icon, |                                 modifier = Modifier.size(24.dp), | ||||||
|                                 transitionSpec = { fadeIn() togetherWith fadeOut() }, |                                 model = ImageRequest.Builder(context = PlatformContext.INSTANCE) | ||||||
|                             ) { icon -> |                                     .data(data = it.icon) | ||||||
|                                 LwaAsyncImage( |                                     .size(size = 48) | ||||||
|                                     modifier = Modifier.size(24.dp), |                                     .build(), | ||||||
|                                     model = ImageRequest.Builder(context = PlatformContext.INSTANCE) |                                 filterQuality = FilterQuality.High, | ||||||
|                                         .data(data = icon) |                                 contentDescription = null, | ||||||
|                                         .size(size = 48) |                             ) | ||||||
|                                         .build(), |  | ||||||
|                                     filterQuality = FilterQuality.High, |  | ||||||
|                                     contentDescription = null, |  | ||||||
|                                 ) |  | ||||||
|                             } |  | ||||||
|                         } |                         } | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -1,77 +1,54 @@ | ||||||
| package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common | package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common | ||||||
| 
 | 
 | ||||||
| import androidx.compose.animation.AnimatedContent | import androidx.compose.animation.AnimatedContent | ||||||
| import androidx.compose.animation.AnimatedVisibility |  | ||||||
| import androidx.compose.animation.fadeIn | import androidx.compose.animation.fadeIn | ||||||
| import androidx.compose.animation.fadeOut | import androidx.compose.animation.fadeOut | ||||||
| import androidx.compose.animation.togetherWith | import androidx.compose.animation.togetherWith | ||||||
| import androidx.compose.foundation.ExperimentalFoundationApi | import androidx.compose.foundation.ExperimentalFoundationApi | ||||||
| import androidx.compose.foundation.background | import androidx.compose.foundation.background | ||||||
| import androidx.compose.foundation.layout.Arrangement |  | ||||||
| import androidx.compose.foundation.layout.Box | import androidx.compose.foundation.layout.Box | ||||||
| import androidx.compose.foundation.layout.Column |  | ||||||
| import androidx.compose.foundation.layout.Row |  | ||||||
| import androidx.compose.foundation.layout.Spacer |  | ||||||
| import androidx.compose.foundation.layout.fillMaxSize | import androidx.compose.foundation.layout.fillMaxSize | ||||||
| import androidx.compose.foundation.layout.offset |  | ||||||
| import androidx.compose.foundation.layout.padding |  | ||||||
| import androidx.compose.foundation.layout.size | import androidx.compose.foundation.layout.size | ||||||
| import androidx.compose.foundation.layout.width |  | ||||||
| import androidx.compose.material.Icon |  | ||||||
| import androidx.compose.material.IconButton |  | ||||||
| import androidx.compose.material.MaterialTheme | import androidx.compose.material.MaterialTheme | ||||||
| import androidx.compose.material.Text |  | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.runtime.Stable | import androidx.compose.runtime.Stable | ||||||
| import androidx.compose.ui.Alignment | import androidx.compose.ui.Alignment | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.draw.clip | import androidx.compose.ui.draw.clip | ||||||
| import androidx.compose.ui.draw.drawWithContent | import androidx.compose.ui.draw.drawWithContent | ||||||
|  | import androidx.compose.ui.graphics.Brush | ||||||
|  | import androidx.compose.ui.graphics.Color | ||||||
| import androidx.compose.ui.graphics.FilterQuality | import androidx.compose.ui.graphics.FilterQuality | ||||||
|  | import androidx.compose.ui.graphics.Shape | ||||||
| import androidx.compose.ui.layout.ContentScale | import androidx.compose.ui.layout.ContentScale | ||||||
| import androidx.compose.ui.unit.Dp |  | ||||||
| import androidx.compose.ui.unit.DpSize | import androidx.compose.ui.unit.DpSize | ||||||
| import androidx.compose.ui.unit.dp |  | ||||||
| import com.pixelized.desktop.lwa.ui.composable.image.LwaAsyncImage | import com.pixelized.desktop.lwa.ui.composable.image.LwaAsyncImage | ||||||
| import com.pixelized.desktop.lwa.ui.composable.shapes.ArrowShape |  | ||||||
| import com.pixelized.desktop.lwa.ui.theme.lwa | import com.pixelized.desktop.lwa.ui.theme.lwa | ||||||
| import lwacharactersheet.composeapp.generated.resources.Res |  | ||||||
| import lwacharactersheet.composeapp.generated.resources.ic_heart_24dp |  | ||||||
| import lwacharactersheet.composeapp.generated.resources.ic_water_drop_24dp |  | ||||||
| import org.jetbrains.compose.resources.painterResource |  | ||||||
| 
 | 
 | ||||||
| @Stable | @Stable | ||||||
| data class CharacterRibbonPortraitUio( | data class CharacterRibbonPortraitUio( | ||||||
|     val portrait: String?, |     val portrait: String?, | ||||||
|     val name: String, |     val name: String, | ||||||
|     val levelUp: Boolean, |     val damagePercent: Float?, | ||||||
|     val stats: StatsDetail?, | ) | ||||||
| ) { |  | ||||||
|     @Stable |  | ||||||
|     data class StatsDetail( |  | ||||||
|         val hp: Int, |  | ||||||
|         val maxHp: Int, |  | ||||||
|         val pp: Int, |  | ||||||
|         val maxPp: Int, |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| @OptIn(ExperimentalFoundationApi::class) | @OptIn(ExperimentalFoundationApi::class) | ||||||
| @Composable | @Composable | ||||||
| fun CharacterRibbonPortrait( | fun CharacterRibbonPortrait( | ||||||
|     modifier: Modifier = Modifier, |     modifier: Modifier = Modifier, | ||||||
|  |     background: Color = MaterialTheme.lwa.colorScheme.elevated.base1dp, | ||||||
|  |     overlay: Brush = MaterialTheme.lwa.colorScheme.portraitBackgroundBrush, | ||||||
|     size: DpSize = MaterialTheme.lwa.dimen.portrait.minimized, |     size: DpSize = MaterialTheme.lwa.dimen.portrait.minimized, | ||||||
|     levelUpOffset: Dp = 9.dp, |  | ||||||
|     character: CharacterRibbonPortraitUio, |     character: CharacterRibbonPortraitUio, | ||||||
|     onLevelUp: () -> Unit, |  | ||||||
| ) { | ) { | ||||||
|     val colorScheme = MaterialTheme.lwa.colorScheme |  | ||||||
| 
 |  | ||||||
|     Box( |     Box( | ||||||
|         modifier = modifier |         modifier = modifier | ||||||
|             .size(size = size) |             .size(size = size) | ||||||
|             .clip(shape = MaterialTheme.lwa.shapes.portrait) |             .background(color = background) | ||||||
|             .background(color = colorScheme.elevated.base1dp) |             .drawWithContent { | ||||||
|  |                 drawContent() | ||||||
|  |                 drawRect(brush = overlay) | ||||||
|  |             } | ||||||
|     ) { |     ) { | ||||||
|         AnimatedContent( |         AnimatedContent( | ||||||
|             targetState = character.portrait, |             targetState = character.portrait, | ||||||
|  | @ -87,95 +64,10 @@ fun CharacterRibbonPortrait( | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         AnimatedVisibility( |         character.damagePercent?.let { | ||||||
|             modifier = Modifier |  | ||||||
|                 .align(alignment = Alignment.TopEnd) |  | ||||||
|                 .offset(x = levelUpOffset, y = -levelUpOffset), |  | ||||||
|             visible = character.levelUp, |  | ||||||
|             enter = fadeIn(), |  | ||||||
|             exit = fadeOut(), |  | ||||||
|         ) { |  | ||||||
|             IconButton( |  | ||||||
|                 onClick = onLevelUp, |  | ||||||
|             ) { |  | ||||||
|                 ArrowShape( |  | ||||||
|                     color = MaterialTheme.lwa.colorScheme.portrait.levelUp, |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         character.stats?.let { stats -> |  | ||||||
| 
 |  | ||||||
|             BloodOverlay( |             BloodOverlay( | ||||||
|                 maxHp = stats.maxHp.toFloat(), |                 damagePercent = it, | ||||||
|                 hp = stats.hp.toFloat(), |  | ||||||
|             ) |             ) | ||||||
| 
 |  | ||||||
|             Column( |  | ||||||
|                 modifier = Modifier |  | ||||||
|                     .fillMaxSize() |  | ||||||
|                     .drawWithContent { |  | ||||||
|                         drawRect(brush = colorScheme.portraitBackgroundBrush) |  | ||||||
|                         drawContent() |  | ||||||
|                     } |  | ||||||
|                     .padding(vertical = 2.dp, horizontal = 4.dp), |  | ||||||
|                 verticalArrangement = Arrangement.aligned(alignment = Alignment.Bottom), |  | ||||||
|             ) { |  | ||||||
|                 Row { |  | ||||||
|                     Icon( |  | ||||||
|                         modifier = Modifier |  | ||||||
|                             .size(12.dp) |  | ||||||
|                             .offset(y = 2.dp), |  | ||||||
|                         painter = painterResource(Res.drawable.ic_heart_24dp), |  | ||||||
|                         contentDescription = null |  | ||||||
|                     ) |  | ||||||
|                     Spacer( |  | ||||||
|                         modifier = Modifier.width(width = 2.dp), |  | ||||||
|                     ) |  | ||||||
|                     Text( |  | ||||||
|                         modifier = Modifier.alignByBaseline(), |  | ||||||
|                         style = MaterialTheme.lwa.typography.portrait.value, |  | ||||||
|                         text = "${stats.hp}", |  | ||||||
|                     ) |  | ||||||
|                     Text( |  | ||||||
|                         modifier = Modifier.alignByBaseline(), |  | ||||||
|                         style = MaterialTheme.lwa.typography.portrait.separator, |  | ||||||
|                         text = "/", |  | ||||||
|                     ) |  | ||||||
|                     Text( |  | ||||||
|                         modifier = Modifier.alignByBaseline(), |  | ||||||
|                         style = MaterialTheme.lwa.typography.portrait.max, |  | ||||||
|                         text = "${stats.maxHp}", |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|                 Row { |  | ||||||
|                     Icon( |  | ||||||
|                         modifier = Modifier |  | ||||||
|                             .size(12.dp) |  | ||||||
|                             .offset(y = 2.dp), |  | ||||||
|                         painter = painterResource(Res.drawable.ic_water_drop_24dp), |  | ||||||
|                         contentDescription = null |  | ||||||
|                     ) |  | ||||||
|                     Spacer( |  | ||||||
|                         modifier = Modifier.width(width = 2.dp), |  | ||||||
|                     ) |  | ||||||
|                     Text( |  | ||||||
|                         modifier = Modifier.alignByBaseline(), |  | ||||||
|                         style = MaterialTheme.lwa.typography.portrait.value, |  | ||||||
|                         text = "${stats.pp}", |  | ||||||
|                     ) |  | ||||||
|                     Text( |  | ||||||
|                         modifier = Modifier.alignByBaseline(), |  | ||||||
|                         style = MaterialTheme.lwa.typography.portrait.separator, |  | ||||||
|                         text = "/", |  | ||||||
|                     ) |  | ||||||
|                     Text( |  | ||||||
|                         modifier = Modifier.alignByBaseline(), |  | ||||||
|                         style = MaterialTheme.lwa.typography.portrait.max, |  | ||||||
|                         text = "${stats.maxPp}", |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -0,0 +1,104 @@ | ||||||
|  | package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.layout.Arrangement | ||||||
|  | import androidx.compose.foundation.layout.Column | ||||||
|  | import androidx.compose.foundation.layout.Row | ||||||
|  | import androidx.compose.foundation.layout.Spacer | ||||||
|  | import androidx.compose.foundation.layout.fillMaxSize | ||||||
|  | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
|  | 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.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.unit.dp | ||||||
|  | import com.pixelized.desktop.lwa.ui.theme.lwa | ||||||
|  | import lwacharactersheet.composeapp.generated.resources.Res | ||||||
|  | import lwacharactersheet.composeapp.generated.resources.ic_heart_24dp | ||||||
|  | import lwacharactersheet.composeapp.generated.resources.ic_water_drop_24dp | ||||||
|  | import org.jetbrains.compose.resources.painterResource | ||||||
|  | 
 | ||||||
|  | @Stable | ||||||
|  | data class CharacterRibbonStatsUio( | ||||||
|  |     val hp: Int, | ||||||
|  |     val maxHp: Int, | ||||||
|  |     val pp: Int, | ||||||
|  |     val maxPp: Int, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun CharacterRibbonStats( | ||||||
|  |     modifier: Modifier = Modifier, | ||||||
|  |     status: CharacterRibbonStatsUio?, | ||||||
|  | ) { | ||||||
|  |     status?.let { status -> | ||||||
|  |         Column( | ||||||
|  |             modifier = Modifier | ||||||
|  |                 .fillMaxWidth() | ||||||
|  |                 .padding(vertical = 2.dp, horizontal = 4.dp) | ||||||
|  |                 .then(other = modifier), | ||||||
|  |             verticalArrangement = Arrangement.aligned(alignment = Alignment.Bottom), | ||||||
|  |         ) { | ||||||
|  |             Row { | ||||||
|  |                 Icon( | ||||||
|  |                     modifier = Modifier | ||||||
|  |                         .size(12.dp) | ||||||
|  |                         .offset(y = 2.dp), | ||||||
|  |                     painter = painterResource(Res.drawable.ic_heart_24dp), | ||||||
|  |                     contentDescription = null | ||||||
|  |                 ) | ||||||
|  |                 Spacer( | ||||||
|  |                     modifier = Modifier.width(width = 2.dp), | ||||||
|  |                 ) | ||||||
|  |                 Text( | ||||||
|  |                     modifier = Modifier.alignByBaseline(), | ||||||
|  |                     style = MaterialTheme.lwa.typography.portrait.value, | ||||||
|  |                     text = "${status.hp}", | ||||||
|  |                 ) | ||||||
|  |                 Text( | ||||||
|  |                     modifier = Modifier.alignByBaseline(), | ||||||
|  |                     style = MaterialTheme.lwa.typography.portrait.separator, | ||||||
|  |                     text = "/", | ||||||
|  |                 ) | ||||||
|  |                 Text( | ||||||
|  |                     modifier = Modifier.alignByBaseline(), | ||||||
|  |                     style = MaterialTheme.lwa.typography.portrait.max, | ||||||
|  |                     text = "${status.maxHp}", | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             Row { | ||||||
|  |                 Icon( | ||||||
|  |                     modifier = Modifier | ||||||
|  |                         .size(size = 12.dp) | ||||||
|  |                         .offset(y = 2.dp), | ||||||
|  |                     painter = painterResource(Res.drawable.ic_water_drop_24dp), | ||||||
|  |                     contentDescription = null | ||||||
|  |                 ) | ||||||
|  |                 Spacer( | ||||||
|  |                     modifier = Modifier.width(width = 2.dp), | ||||||
|  |                 ) | ||||||
|  |                 Text( | ||||||
|  |                     modifier = Modifier.alignByBaseline(), | ||||||
|  |                     style = MaterialTheme.lwa.typography.portrait.value, | ||||||
|  |                     text = "${status.pp}", | ||||||
|  |                 ) | ||||||
|  |                 Text( | ||||||
|  |                     modifier = Modifier.alignByBaseline(), | ||||||
|  |                     style = MaterialTheme.lwa.typography.portrait.separator, | ||||||
|  |                     text = "/", | ||||||
|  |                 ) | ||||||
|  |                 Text( | ||||||
|  |                     modifier = Modifier.alignByBaseline(), | ||||||
|  |                     style = MaterialTheme.lwa.typography.portrait.max, | ||||||
|  |                     text = "${status.maxPp}", | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -7,6 +7,8 @@ class CharacterRibbonUio( | ||||||
|     val characterSheetId: String, |     val characterSheetId: String, | ||||||
|     val hideOverruled: Boolean, |     val hideOverruled: Boolean, | ||||||
|     val enableDetail: Boolean, |     val enableDetail: Boolean, | ||||||
|  |     val levelUp: Boolean, | ||||||
|     val portrait: CharacterRibbonPortraitUio, |     val portrait: CharacterRibbonPortraitUio, | ||||||
|     val status: List<List<CharacterRibbonAlterationUio>>, |     val status: CharacterRibbonStatsUio?, | ||||||
|  |     val alterations: List<List<CharacterRibbonAlterationUio>>, | ||||||
| ) | ) | ||||||
|  | @ -1,67 +1,125 @@ | ||||||
| //package com.pixelized.desktop.lwa.ui.shaders | package com.pixelized.desktop.lwa.ui.shaders | ||||||
| // | 
 | ||||||
| //import androidx.compose.animation.core.withInfiniteAnimationFrameMillis | import androidx.compose.animation.core.withInfiniteAnimationFrameMillis | ||||||
| //import androidx.compose.runtime.Composable | import androidx.compose.foundation.border | ||||||
| //import androidx.compose.runtime.getValue | import androidx.compose.runtime.Composable | ||||||
| //import androidx.compose.runtime.mutableStateOf | import androidx.compose.runtime.DisposableEffect | ||||||
| //import androidx.compose.runtime.produceState | import androidx.compose.runtime.LaunchedEffect | ||||||
| //import androidx.compose.runtime.remember | import androidx.compose.runtime.getValue | ||||||
| //import androidx.compose.runtime.setValue | import androidx.compose.runtime.mutableStateOf | ||||||
| //import androidx.compose.ui.Modifier | import androidx.compose.runtime.produceState | ||||||
| //import androidx.compose.ui.draw.drawBehind | import androidx.compose.runtime.remember | ||||||
| //import androidx.compose.ui.draw.drawWithContent | import androidx.compose.runtime.setValue | ||||||
| //import androidx.compose.ui.geometry.Size | import androidx.compose.ui.Modifier | ||||||
| //import androidx.compose.ui.graphics.BlendMode | import androidx.compose.ui.draw.drawWithContent | ||||||
| //import androidx.compose.ui.graphics.Brush | import androidx.compose.ui.geometry.Size | ||||||
| //import androidx.compose.ui.graphics.Color | import androidx.compose.ui.graphics.BlendMode | ||||||
| //import androidx.compose.ui.layout.onGloballyPositioned | import androidx.compose.ui.graphics.Brush | ||||||
| //import com.mikepenz.hypnoticcanvas.NonAndroidRuntimeEffect | import androidx.compose.ui.graphics.Color | ||||||
| //import com.mikepenz.hypnoticcanvas.shaders.Shader | import androidx.compose.ui.graphics.RectangleShape | ||||||
| //import com.mikepenz.hypnoticcanvas.utils.round | import androidx.compose.ui.graphics.Shape | ||||||
| // | import androidx.compose.ui.layout.onGloballyPositioned | ||||||
| //@Composable | import androidx.compose.ui.unit.Dp | ||||||
| //fun Modifier.shaderContent( | import androidx.compose.ui.unit.dp | ||||||
| //    shader: Shader, | import com.mikepenz.hypnoticcanvas.NonAndroidRuntimeEffect | ||||||
| //    speed: Float = 1f, | import com.mikepenz.hypnoticcanvas.shaders.Shader | ||||||
| //    fallback: () -> Brush = { | import com.mikepenz.hypnoticcanvas.utils.round | ||||||
| //        Brush.horizontalGradient(listOf(Color.Transparent, Color.Transparent)) | 
 | ||||||
| //    }, | @Composable | ||||||
| //): Modifier { | fun Modifier.shaderContent( | ||||||
| //    val runtimeEffect = remember(shader) { NonAndroidRuntimeEffect(shader) } |     shader: Shader, | ||||||
| //    var size: Size by remember { mutableStateOf(Size(-1f, -1f)) } |     speed: Float = 1f, | ||||||
| //    val speedModifier = shader.speedModifier |     fallback: () -> Brush = { | ||||||
| // |         Brush.horizontalGradient(listOf(Color.Transparent, Color.Transparent)) | ||||||
| //    val time by if (runtimeEffect.supported) { |     }, | ||||||
| //        var startMillis = remember(shader) { -1L } | ): Modifier { | ||||||
| //        produceState(0f, speedModifier) { |     val runtimeEffect = remember(shader) { NonAndroidRuntimeEffect(shader) } | ||||||
| //            while (true) { |     var size: Size by remember { mutableStateOf(Size(-1f, -1f)) } | ||||||
| //                withInfiniteAnimationFrameMillis { |     val speedModifier = shader.speedModifier | ||||||
| //                    if (startMillis < 0) startMillis = it | 
 | ||||||
| //                    value = ((it - startMillis) / 16.6f) / 10f |     val time by if (runtimeEffect.supported) { | ||||||
| //                } |         var startMillis = remember(shader) { -1L } | ||||||
| //            } |         produceState(0f, speedModifier) { | ||||||
| //        } |             while (true) { | ||||||
| //    } else { |                 withInfiniteAnimationFrameMillis { | ||||||
| //        mutableStateOf(-1f) |                     if (startMillis < 0) startMillis = it | ||||||
| //    } |                     value = ((it - startMillis) / 16.6f) / 10f | ||||||
| // |                 } | ||||||
| //    return this then Modifier |             } | ||||||
| //        .onGloballyPositioned { |         } | ||||||
| //            size = Size(it.size.width.toFloat(), it.size.height.toFloat()) |     } else { | ||||||
| //        } |         mutableStateOf(-1f) | ||||||
| //        .drawWithContent { |     } | ||||||
| //            drawContent() | 
 | ||||||
| //            // set uniforms for the shaders |     return this then Modifier | ||||||
| //            runtimeEffect.update( |         .onGloballyPositioned { | ||||||
| //                shader = shader, |             size = Size(it.size.width.toFloat(), it.size.height.toFloat()) | ||||||
| //                time = (time * speed * speedModifier).round(3), |         } | ||||||
| //                width = size.width, |         .drawWithContent { | ||||||
| //                height = size.height |             drawContent() | ||||||
| //            ) |             // set uniforms for the shaders | ||||||
| //            if (runtimeEffect.ready) { |             runtimeEffect.update( | ||||||
| //                drawRect(brush = runtimeEffect.build(), blendMode = BlendMode.SrcAtop) |                 shader = shader, | ||||||
| //            } else { |                 time = (time * speed * speedModifier).round(3), | ||||||
| //                drawRect(brush = fallback()) |                 width = size.width, | ||||||
| //            } |                 height = size.height | ||||||
| //        } |             ) | ||||||
| //} |             if (runtimeEffect.ready) { | ||||||
|  |                 drawRect(brush = runtimeEffect.build(), blendMode = BlendMode.SrcAtop) | ||||||
|  |             } else { | ||||||
|  |                 drawRect(brush = fallback()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun Modifier.shaderBorder( | ||||||
|  |     width: Dp = 1.dp, | ||||||
|  |     shape: Shape = RectangleShape, | ||||||
|  |     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()) | ||||||
|  |         } | ||||||
|  |         .border( | ||||||
|  |             width = width, | ||||||
|  |             shape = shape, | ||||||
|  |             brush = run { | ||||||
|  |                 // set uniforms for the shaders | ||||||
|  |                 runtimeEffect.update( | ||||||
|  |                     shader = shader, | ||||||
|  |                     time = (time * speed * speedModifier).round(3), | ||||||
|  |                     width = size.width, | ||||||
|  |                     height = size.height | ||||||
|  |                 ) | ||||||
|  |                 if (runtimeEffect.ready) { | ||||||
|  |                     runtimeEffect.build() | ||||||
|  |                 } else { | ||||||
|  |                     fallback() | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,43 @@ | ||||||
|  | package com.pixelized.desktop.lwa.ui.shaders.packs | ||||||
|  | 
 | ||||||
|  | import com.mikepenz.hypnoticcanvas.shaders.Shader | ||||||
|  | 
 | ||||||
|  | data object GoldenFlow : Shader { | ||||||
|  |     override val name: String | ||||||
|  |         get() = "" | ||||||
|  |     override val authorName: String | ||||||
|  |         get() = "" | ||||||
|  |     override val authorUrl: String | ||||||
|  |         get() = "" | ||||||
|  |     override val credit: String | ||||||
|  |         get() = "" | ||||||
|  |     override val license: String | ||||||
|  |         get() = "" | ||||||
|  |     override val licenseUrl: String | ||||||
|  |         get() = "" | ||||||
|  | 
 | ||||||
|  |     override val sksl: String | ||||||
|  |         get() = """ | ||||||
|  | uniform float uTime; | ||||||
|  | uniform vec3 uResolution; | ||||||
|  | 
 | ||||||
|  | vec4 main(vec2 fragCoord) { | ||||||
|  |     vec2 q = 7.0 * (fragCoord.xy - 0.5 * uResolution.xy) / max(uResolution.x, uResolution.y); | ||||||
|  |      | ||||||
|  |     float i = 1.0; | ||||||
|  |     for(int iter = 0; iter < 40; iter++) | ||||||
|  |     { | ||||||
|  |         vec2 o = q; | ||||||
|  |         o.x += (0.5 / i) * cos(i * q.y + uTime * 0.297 + 0.03 * i) + 1.3;         | ||||||
|  |         o.y += (0.5 / i) * cos(i * q.x + uTime * 0.414 + 0.03 * (i + 10.0)) + 1.9; | ||||||
|  |         q = o; | ||||||
|  |         i *= 1.1; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     vec3 col = vec3(0.5 * sin(3.0 * q.x) + 0.5, 0.5 * sin(3.0 * q.y) + 0.5, sin(1.3 * q.x + 1.7 * q.y)); | ||||||
|  |     float f = 0.43 * (col.x + col.y + col.z); | ||||||
|  |      | ||||||
|  |     return vec4(f + 0.6, 0.2 + 0.75 * f, 0.2, 0.5); | ||||||
|  | } | ||||||
|  | """.trimIndent() | ||||||
|  | } | ||||||
|  | @ -13,6 +13,7 @@ coil = "3.1.0" | ||||||
| zoomable = "2.7.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" | ||||||
|  | shader = "0.3.0" | ||||||
| 
 | 
 | ||||||
| [plugins] | [plugins] | ||||||
| composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } | composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } | ||||||
|  | @ -37,6 +38,9 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- | ||||||
| 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" } | engawapg-zoomable = { module = "net.engawapg.lib:zoomable", version.ref = "zoomable" } | ||||||
|  | # Shader | ||||||
|  | hypnoticcanvas  = { module = "com.mikepenz.hypnoticcanvas:hypnoticcanvas", version.ref = "shader" } | ||||||
|  | hypnoticcanvas-shaders = { module = "com.mikepenz.hypnoticcanvas:hypnoticcanvas-shaders", version.ref = "shader" } | ||||||
| # 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