Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,26 @@ class PlayerCellBasicBody extends StatelessWidget {
return PlayerCellSideTaps(
child: Stack(
children: [
// Commander damage chips, pinned to the top-left.
Positioned(
top: 0,
bottom: PlayerCellBottom.iconButtonsSize,
left: inverted ? 0 : null,
right: !inverted ? 0 : null,
left: 0,
child: PlayerCellQuickInfo(
playerIndex: playerIndex,
axisAlignment: inverted ? 1 : -1,
axisAlignment: 1,
group: QuickInfoGroup.commanderDamage,
),
),
// Counters, statuses and casts, pinned to the top-right.
Positioned(
top: 0,
bottom: PlayerCellBottom.iconButtonsSize,
right: 0,
child: PlayerCellQuickInfo(
playerIndex: playerIndex,
axisAlignment: -1,
group: QuickInfoGroup.other,
),
),
Positioned.fill(
Expand Down
221 changes: 137 additions & 84 deletions lib/widgets/arena/player_cell/components/player_cell_quick_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:counter_spell/models/interaction/interaction_mode.dart';
import 'package:counter_spell/widgets/arena/player_cell/builders/cell_mode_builder.dart';
import 'package:counter_spell/widgets/body/players_list_view/player_tile/components/split_theme.dart';
import 'package:counter_spell/widgets/components/builders/cell_mode_and_increment_builder.dart';
import 'package:counter_spell/widgets/components/builders/player_cards_and_themes_builder.dart';
import 'package:counter_spell/widgets/components/project/delta_chip.dart';
import 'package:flutter/material.dart';
import 'package:sid_base/sid_base.dart';
Expand All @@ -16,15 +17,27 @@ extension on String {
String get initial => isEmpty ? this : this[0];
}

/// Which subset of quick-info chips a [PlayerCellQuickInfo] renders, so the two
/// groups can be pinned to opposite sides of the cell.
enum QuickInfoGroup {
/// Commander damage taken (and dealt, when [PlayerCellQuickInfo.showDamageDealt]).
commanderDamage,

/// Counters, statuses and commander casts.
other,
}

class PlayerCellQuickInfo extends StatelessWidget {
const PlayerCellQuickInfo({
super.key,
required this.playerIndex,
required this.axisAlignment,
required this.group,
this.showDamageDealt = false,
});
final int playerIndex;
final double axisAlignment;
final QuickInfoGroup group;
final bool showDamageDealt;

@override
Expand All @@ -38,6 +51,7 @@ class PlayerCellQuickInfo extends StatelessWidget {
playerIndex: playerIndex,
playerSettings: game.settings.playerSettings,
playerStates: game.currentState.playerStates,
group: group,
showDamageDealt: showDamageDealt,
),
),
Expand All @@ -60,19 +74,20 @@ class _PlayerCellQuickInfo extends StatelessWidget {
required this.playerIndex,
required this.playerSettings,
required this.playerStates,
required this.group,
required this.showDamageDealt,
});

final int playerIndex;
final List<PlayerState> playerStates;
final List<PlayerSettings> playerSettings;
final QuickInfoGroup group;
final bool showDamageDealt;

@override
Widget build(BuildContext context) {
final PlayerSettings thisPlayerSettings = playerSettings[playerIndex];
final PlayerState thisPlayerState = playerStates[playerIndex];
final int playerCount = playerStates.length;

final List<CommanderDamage> damageDealt = [
for (final playerStateDelta in playerStates)
Expand All @@ -81,15 +96,95 @@ class _PlayerCellQuickInfo extends StatelessWidget {

final theme = context.theme;
final layout = theme.layout;
final isEmpty =
thisPlayerState.copyWith(lifePoints: 0) ==
PlayerState.start(startingLifeTotal: 0, playerCount: playerCount) &&
(!showDamageDealt ||
damageDealt.every(
(element) => element == (fromPartnerA: 0, fromPartnerB: 0),
));

if (isEmpty) return const SizedBox.shrink();
final List<Widget> chips = switch (group) {
QuickInfoGroup.commanderDamage => <Widget>[
for (
int i = 0;
i < thisPlayerState.commanderDamageTaken.length;
i++
) ...[
if (thisPlayerState.commanderDamageTaken[i].fromPartnerA
case int damage)
if (damage != 0)
_CommanderDamageChip(
attackerIndex: i,
fromPartnerA: true,
icon: CounterSpellIcons.defense_filled,
result: damage,
note: switch (playerSettings[i].runsTwoPartners) {
true => 'by ${playerSettings[i].name.initial} (A)',
false => 'by ${playerSettings[i].name.initial}',
},
),
if (thisPlayerState.commanderDamageTaken[i].fromPartnerB
case int damage)
if (damage != 0)
_CommanderDamageChip(
attackerIndex: i,
fromPartnerA: false,
icon: CounterSpellIcons.defense_filled,
result: damage,
note: 'by ${playerSettings[i].name.initial} (B)',
),
],
if (showDamageDealt)
for (int i = 0; i < damageDealt.length; i++) ...[
if (damageDealt[i].fromPartnerA case int damage)
if (damage != 0)
_CommanderDamageChip(
attackerIndex: playerIndex,
fromPartnerA: true,
icon: CounterSpellIcons.attack,
result: damage,
note: switch (thisPlayerSettings.runsTwoPartners) {
true => 'to ${playerSettings[i].name.initial} (A)',
false => 'to ${playerSettings[i].name.initial}',
},
),
if (damageDealt[i].fromPartnerB case int damage)
if (damage != 0)
_CommanderDamageChip(
attackerIndex: playerIndex,
fromPartnerA: false,
icon: CounterSpellIcons.attack,
result: damage,
note: 'to ${playerSettings[i].name.initial} (B)',
),
],
],
QuickInfoGroup.other => <Widget>[
if (thisPlayerState.counters.isNotEmpty)
for (final counter in Counter.values)
if (thisPlayerState.counters[counter] case int amount)
if (amount != 0)
DeltaChip.result(
icon: counter.filledIcon,
result: amount,
boolean: counter.isBoolean,
),
if (thisPlayerState.commanderCasts.partnerA case int castsA)
if (castsA != 0)
DeltaChip.result(
icon: InteractionMode.cast.filledIcon,
note: switch (thisPlayerSettings.runsTwoPartners) {
true => 'A',
false => null,
},
result: castsA,
),
if (thisPlayerState.commanderCasts.partnerB case int castsB)
if (castsB != 0)
DeltaChip.result(
icon: InteractionMode.cast.filledIcon,
note: 'B',
result: castsB,
),
// TODO: add is dead chip
],
};

if (chips.isEmpty) return const SizedBox.shrink();

return SingleChildScrollView(
physics: CallbackScrollPhysics(
Expand All @@ -103,83 +198,41 @@ class _PlayerCellQuickInfo extends StatelessWidget {
vertical: layout.padding.smaller,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (thisPlayerState.counters.isNotEmpty)
for (final counter in Counter.values)
if (thisPlayerState.counters[counter] case int amount)
if (amount != 0)
DeltaChip.result(
icon: counter.filledIcon,
result: amount,
boolean: counter.isBoolean,
),
for (
int i = 0;
i < thisPlayerState.commanderDamageTaken.length;
i++
) ...[
if (thisPlayerState.commanderDamageTaken[i].fromPartnerA
case int damage)
if (damage != 0)
DeltaChip.result(
icon: CounterSpellIcons.defense_filled,
result: damage,
note: switch (playerSettings[i].runsTwoPartners) {
true => 'by ${playerSettings[i].name.initial} (A)',
false => 'by ${playerSettings[i].name.initial}',
},
),
if (thisPlayerState.commanderDamageTaken[i].fromPartnerB
case int damage)
if (damage != 0)
DeltaChip.result(
icon: CounterSpellIcons.defense_filled,
note: 'by ${playerSettings[i].name.initial} (B)',
result: damage,
),
],
if (showDamageDealt)
for (int i = 0; i < damageDealt.length; i++) ...[
if (damageDealt[i].fromPartnerA case int damage)
if (damage != 0)
DeltaChip.result(
icon: CounterSpellIcons.attack,
result: damage,
note: switch (thisPlayerSettings.runsTwoPartners) {
true => 'to ${playerSettings[i].name.initial} (A)',
false => 'to ${playerSettings[i].name.initial}',
},
),
if (damageDealt[i].fromPartnerB case int damage)
if (damage != 0)
DeltaChip.result(
icon: CounterSpellIcons.attack,
note: 'to ${playerSettings[i].name.initial} (B)',
result: damage,
),
],

if (thisPlayerState.commanderCasts.partnerA case int castsA)
if (castsA != 0)
DeltaChip.result(
icon: InteractionMode.cast.filledIcon,
note: switch (thisPlayerSettings.runsTwoPartners) {
true => 'A',
false => null,
},
result: castsA,
),
if (thisPlayerState.commanderCasts.partnerB case int castsB)
if (castsB != 0)
DeltaChip.result(
icon: InteractionMode.cast.filledIcon,
note: 'B',
result: castsB,
),
// TODO: add is dead chip
].separateWith(Space.vertical(layout.spacing.tiny)),
children: chips.separateWith(Space.vertical(layout.spacing.tiny)),
),
),
);
}
}

/// A commander-damage [DeltaChip] themed by the commander that applied the
/// damage — [attackerIndex]'s partner A or B theme — so the pill takes that
/// commander's container color with a legible on-container foreground.
class _CommanderDamageChip extends StatelessWidget {
const _CommanderDamageChip({
required this.attackerIndex,
required this.fromPartnerA,
required this.icon,
required this.result,
this.note,
});

final int attackerIndex;
final bool fromPartnerA;
final IconData icon;
final int result;
final String? note;

@override
Widget build(BuildContext context) {
return PlayerCardsAndThemesBuilder(
playerIndex: attackerIndex,
builder: (context, cardA, cardB, themeA, themeB, child) {
return Theme(
data: fromPartnerA ? themeA : themeB,
child: DeltaChip.result(icon: icon, result: result, note: note),
);
},
);
}
}
13 changes: 10 additions & 3 deletions lib/widgets/components/builders/card_theme_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,17 @@ class _CardThemeBuilderState extends State<_CardThemeBuilder> {
@override
void didUpdateWidget(covariant _CardThemeBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.variant != widget.variant ||
final bool cardChanged = oldWidget.card.id != widget.card.id;
if (cardChanged) {
// Fall back to the new card's color-identity seed right away so the
// previous commander's color doesn't linger while the artwork-derived
// scheme is computed asynchronously.
colorScheme = widget.card.colorSchemes(widget.appTheme).first;
}
if (cardChanged ||
oldWidget.variant != widget.variant ||
oldWidget.contrast != widget.contrast ||
widget.appTheme.brightness != oldWidget.appTheme.brightness ||
(oldWidget.card.id != widget.card.id)) {
widget.appTheme.brightness != oldWidget.appTheme.brightness) {
_updateColorScheme();
}
}
Expand Down