From c57ec02f67c5718f0ff8cc2e7751d5c931b56a82 Mon Sep 17 00:00:00 2001 From: shahondin1624 Date: Fri, 13 Mar 2026 13:28:26 +0100 Subject: [PATCH] feat: derived attributes display panel (Closes #11) Add DerivedAttributesPanel showing initiative, matrix initiative, composure, judge intent, memory, carry, run, and physical/mental/ social limits. Uses existing Attributes calculation methods. Compact shows 2 columns, Medium 3, Expanded 5. Placed between resources and attributes grid. Co-Authored-By: Claude Opus 4.6 --- .../kotlin/org/shahondin1624/App.kt | 3 + .../charactermodel/DerivedAttributesPanel.kt | 112 ++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 sharedUI/src/commonMain/kotlin/org/shahondin1624/lib/components/charactermodel/DerivedAttributesPanel.kt diff --git a/sharedUI/src/commonMain/kotlin/org/shahondin1624/App.kt b/sharedUI/src/commonMain/kotlin/org/shahondin1624/App.kt index 3dbf12a..0015371 100644 --- a/sharedUI/src/commonMain/kotlin/org/shahondin1624/App.kt +++ b/sharedUI/src/commonMain/kotlin/org/shahondin1624/App.kt @@ -27,6 +27,7 @@ import org.jetbrains.compose.ui.tooling.preview.Preview import org.shahondin1624.lib.components.TestTags import org.shahondin1624.lib.components.UiConstants import org.shahondin1624.lib.components.charactermodel.CharacterHeader +import org.shahondin1624.lib.components.charactermodel.DerivedAttributesPanel import org.shahondin1624.lib.components.charactermodel.ResourcePanel import org.shahondin1624.lib.components.charactermodel.attributespage.AttributesPage import org.shahondin1624.lib.components.settings.SettingsPage @@ -311,6 +312,8 @@ private fun MainScaffold( Spacer(Modifier.height(contentPadding)) ResourcePanel(EXAMPLE_CHARACTER.characterData, EXAMPLE_CHARACTER.attributes.edge) Spacer(Modifier.height(contentPadding)) + DerivedAttributesPanel(EXAMPLE_CHARACTER.attributes) + Spacer(Modifier.height(contentPadding)) AttributesPage(EXAMPLE_CHARACTER) } } diff --git a/sharedUI/src/commonMain/kotlin/org/shahondin1624/lib/components/charactermodel/DerivedAttributesPanel.kt b/sharedUI/src/commonMain/kotlin/org/shahondin1624/lib/components/charactermodel/DerivedAttributesPanel.kt new file mode 100644 index 0000000..d7c21fe --- /dev/null +++ b/sharedUI/src/commonMain/kotlin/org/shahondin1624/lib/components/charactermodel/DerivedAttributesPanel.kt @@ -0,0 +1,112 @@ +package org.shahondin1624.lib.components.charactermodel + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import org.shahondin1624.lib.components.TestTags +import org.shahondin1624.lib.components.UiConstants +import org.shahondin1624.model.attributes.Attributes +import org.shahondin1624.theme.LocalWindowSizeClass +import org.shahondin1624.theme.WindowSizeClass + +/** + * Displays derived attributes calculated from base attributes: + * initiative, matrix initiative, composure, judge intent, memory, + * carry, run, physical/mental/social limits. + */ +@Composable +fun DerivedAttributesPanel(attributes: Attributes) { + val windowSizeClass = LocalWindowSizeClass.current + val padding = UiConstants.Spacing.medium(windowSizeClass) + val spacing = UiConstants.Spacing.small(windowSizeClass) + + val derivedValues = listOf( + "Initiative" to attributes.initiative(), + "Matrix Init." to attributes.matrixInitiative(), + "Composure" to attributes.composure(), + "Judge Intent" to attributes.judgeIntent(), + "Memory" to attributes.memory(), + "Carry" to attributes.carry(), + "Run" to attributes.run(), + "Physical Limit" to attributes.physicalLimit(), + "Mental Limit" to attributes.mentalLimit(), + "Social Limit" to attributes.socialLimit() + ) + + Card( + modifier = Modifier + .fillMaxWidth() + .testTag(TestTags.PANEL_DERIVED_ATTRIBUTES) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(padding) + ) { + Text( + text = "Derived Attributes", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(bottom = spacing) + ) + + val columns = when (windowSizeClass) { + WindowSizeClass.Compact -> 2 + WindowSizeClass.Medium -> 3 + WindowSizeClass.Expanded -> 5 + } + + val rows = derivedValues.chunked(columns) + for (row in rows) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(spacing) + ) { + for (item in row) { + DerivedItem( + label = item.first, + value = item.second, + modifier = Modifier.weight(1f) + ) + } + // Fill remaining space if row is incomplete + repeat(columns - row.size) { + Spacer(Modifier.weight(1f)) + } + } + Spacer(Modifier.height(spacing)) + } + } + } +} + +@Composable +private fun DerivedItem(label: String, value: Int, modifier: Modifier = Modifier) { + Surface( + modifier = modifier, + shape = MaterialTheme.shapes.small, + color = MaterialTheme.colorScheme.surfaceVariant + ) { + Column( + modifier = Modifier.padding(horizontal = 8.dp, vertical = 6.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = label, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1 + ) + Text( + text = value.toString(), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + } + } +} -- 2.49.1