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 <noreply@anthropic.com>
This commit is contained in:
shahondin1624
2026-03-13 13:28:26 +01:00
parent 52bfeb4a3b
commit c57ec02f67
2 changed files with 115 additions and 0 deletions

View File

@@ -27,6 +27,7 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
import org.shahondin1624.lib.components.TestTags import org.shahondin1624.lib.components.TestTags
import org.shahondin1624.lib.components.UiConstants import org.shahondin1624.lib.components.UiConstants
import org.shahondin1624.lib.components.charactermodel.CharacterHeader 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.ResourcePanel
import org.shahondin1624.lib.components.charactermodel.attributespage.AttributesPage import org.shahondin1624.lib.components.charactermodel.attributespage.AttributesPage
import org.shahondin1624.lib.components.settings.SettingsPage import org.shahondin1624.lib.components.settings.SettingsPage
@@ -311,6 +312,8 @@ private fun MainScaffold(
Spacer(Modifier.height(contentPadding)) Spacer(Modifier.height(contentPadding))
ResourcePanel(EXAMPLE_CHARACTER.characterData, EXAMPLE_CHARACTER.attributes.edge) ResourcePanel(EXAMPLE_CHARACTER.characterData, EXAMPLE_CHARACTER.attributes.edge)
Spacer(Modifier.height(contentPadding)) Spacer(Modifier.height(contentPadding))
DerivedAttributesPanel(EXAMPLE_CHARACTER.attributes)
Spacer(Modifier.height(contentPadding))
AttributesPage(EXAMPLE_CHARACTER) AttributesPage(EXAMPLE_CHARACTER)
} }
} }

View File

@@ -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
)
}
}
}